1{-# LANGUAGE BangPatterns, OverloadedStrings #-}
4-- Module: Data.Aeson.Parser
5-- Copyright: (c) 2011 MailRank, Inc.
7-- Maintainer: Bryan O'Sullivan <bos@mailrank.com>
8-- Stability: experimental
9-- Portability: portable
11-- Efficiently and correctly parse a JSON string.
13module Data.Aeson.Parser
19import Blaze.ByteString.Builder (fromByteString, toByteString)
20import Blaze.ByteString.Builder.Char.Utf8 (fromChar)
21import Blaze.ByteString.Builder.Word (fromWord8)
22import Control.Applicative as A
23import Data.Aeson.Types (Value(..))
24import Data.Attoparsec.Char8
25import Data.Bits ((.|.), shiftL)
26import Data.ByteString as B
29import Data.Monoid (mappend, mempty)
31import Data.Text.Encoding (decodeUtf8)
32import Data.Vector as Vector hiding ((++))
33import Data.Word (Word8)
34import qualified Data.Attoparsec as A
35import qualified Data.Attoparsec.Zepto as Z
36import qualified Data.ByteString.Unsafe as B
38-- | Parse a top-level JSON value. This must be either an object or
42 c <- skipSpace *> anyChar
46 _ -> fail "root value is not an object or array"
48object_ :: Parser Value
49object_ = {-# SCC "object_" #-} do
52 a <- jstring <* skipSpace
53 b <- char ':' *> skipSpace *> value
55 vals <- ((pair <* skipSpace) `sepBy` (char ',' *> skipSpace)) <* char '}'
56 return . Object $ Map.fromList vals
59array_ = {-# SCC "array_" #-} do
61 vals <- ((value <* skipSpace) `sepBy` (char ',' *> skipSpace)) <* char ']'
62 return . Array $ Vector.fromList vals
64-- | Parse any JSON value. Use 'json' in preference to this function
65-- if you are parsing data from an untrusted source.
67value = most <|> (Number <$> number)
74 '"' -> String <$> jstring_
75 'f' -> string "alse" *> pure (Bool False)
76 't' -> string "rue" *> pure (Bool True)
77 'n' -> string "ull" *> pure Null
78 _ -> fail "not a valid JSON value"
80doubleQuote, backslash :: Word8
83{-# INLINE backslash #-}
84{-# INLINE doubleQuote #-}
87jstring = A.word8 doubleQuote *> jstring_
89-- | Parse a string without a leading quote.
90jstring_ :: Parser Text
91jstring_ = {-# SCC "jstring_" #-} do
92 s <- A.scan False $ \s c -> if s then Just False
93 else if c == doubleQuote
95 else Just (c == backslash)
96 _ <- A.word8 doubleQuote
97 if backslash `B.elem` s
98 then case Z.parse unescape s of
99 Right r -> return (decodeUtf8 r)
101 else return (decodeUtf8 s)
102{-# INLINE jstring_ #-}
104unescape :: Z.Parser ByteString
105unescape = toByteString <$> go mempty where
107 h <- Z.takeWhile (/=backslash)
110 let !slash = B.unsafeHead start
111 !t = B.unsafeIndex start 1
112 escape = case B.findIndex (==t) "\"\\/ntbrfu" of
115 if slash /= backslash || escape == 255
116 then fail "invalid JSON escape sequence"
118 let cont m = go (acc `mappend` fromByteString h `mappend` m)
121 then cont (fromWord8 (B.unsafeIndex mapping escape))
124 if a < 0xd800 || a > 0xdfff
125 then cont (fromChar (chr a))
127 b <- Z.string "\\u" *> hexQuad
128 if a <= 0xdbff && b >= 0xdc00 && b <= 0xdfff
129 then let !c = ((a - 0xd800) `shiftL` 10) +
130 (b - 0xdc00) + 0x10000
131 in cont (fromChar (chr c))
132 else fail "invalid UTF-16 surrogates"
135 then return (acc `mappend` fromByteString h)
137 mapping = "\"\\/\n\t\b\r\f"
139hexQuad :: Z.Parser Int
142 let hex n | w >= 48 && w <= 57 = w - 48
143 | w >= 97 && w <= 122 = w - 87
144 | w >= 65 && w <= 90 = w - 55
146 where w = fromIntegral $ B.unsafeIndex s n
147 a = hex 0; b = hex 1; c = hex 2; d = hex 3
148 if (a .|. b .|. c .|. d) /= 255
149 then return $! d .|. (c `shiftL` 4) .|. (b `shiftL` 8) .|. (a `shiftL` 12)
150 else fail "invalid hex escape"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list