aeson 0.3.2.14 → 0.4.0.0
raw patch · 26 files changed
+3008/−1577 lines, 26 filesdep +QuickCheckdep +aesondep +dlistdep ~attoparsecdep ~basedep ~template-haskellPVP ok
version bump matches the API change (PVP)
Dependencies added: QuickCheck, aeson, dlist, ghc-prim, test-framework, test-framework-quickcheck2
Dependency ranges changed: attoparsec, base, template-haskell, text, vector
API changes (from Hackage documentation)
- Data.Aeson.Types: genericFromJSON :: Data a => Value -> Result a
- Data.Aeson.Types: genericToJSON :: Data a => a -> Value
- Data.Aeson.Types: instance [incoherent] (Eq a, Hashable a, FromJSON a) => FromJSON (HashSet a)
- Data.Aeson.Types: instance [incoherent] (FromJSON a, FromJSON b) => FromJSON (Either a b)
- Data.Aeson.Types: instance [incoherent] (FromJSON a, FromJSON b) => FromJSON (a, b)
- Data.Aeson.Types: instance [incoherent] (FromJSON a, FromJSON b, FromJSON c) => FromJSON (a, b, c)
- Data.Aeson.Types: instance [incoherent] (Ord a, FromJSON a) => FromJSON (Set a)
- Data.Aeson.Types: instance [incoherent] (Prim a, FromJSON a) => FromJSON (Vector a)
- Data.Aeson.Types: instance [incoherent] (Prim a, ToJSON a) => ToJSON (Vector a)
- Data.Aeson.Types: instance [incoherent] (Storable a, FromJSON a) => FromJSON (Vector a)
- Data.Aeson.Types: instance [incoherent] (Storable a, ToJSON a) => ToJSON (Vector a)
- Data.Aeson.Types: instance [incoherent] (ToJSON a, ToJSON b) => ToJSON (Either a b)
- Data.Aeson.Types: instance [incoherent] (ToJSON a, ToJSON b) => ToJSON (a, b)
- Data.Aeson.Types: instance [incoherent] (ToJSON a, ToJSON b, ToJSON c) => ToJSON (a, b, c)
- Data.Aeson.Types: instance [incoherent] (Vector Vector a, FromJSON a) => FromJSON (Vector a)
- Data.Aeson.Types: instance [incoherent] (Vector Vector a, ToJSON a) => ToJSON (Vector a)
- Data.Aeson.Types: instance [incoherent] Alternative Parser
- Data.Aeson.Types: instance [incoherent] Alternative Result
- Data.Aeson.Types: instance [incoherent] Applicative Parser
- Data.Aeson.Types: instance [incoherent] Applicative Result
- Data.Aeson.Types: instance [incoherent] Data Value
- Data.Aeson.Types: instance [incoherent] Eq DotNetTime
- Data.Aeson.Types: instance [incoherent] Eq Value
- Data.Aeson.Types: instance [incoherent] Eq a => Eq (Result a)
- Data.Aeson.Types: instance [incoherent] FormatTime DotNetTime
- Data.Aeson.Types: instance [incoherent] FromJSON ()
- Data.Aeson.Types: instance [incoherent] FromJSON (Ratio Integer)
- Data.Aeson.Types: instance [incoherent] FromJSON Bool
- Data.Aeson.Types: instance [incoherent] FromJSON ByteString
- Data.Aeson.Types: instance [incoherent] FromJSON Char
- Data.Aeson.Types: instance [incoherent] FromJSON DotNetTime
- Data.Aeson.Types: instance [incoherent] FromJSON Double
- Data.Aeson.Types: instance [incoherent] FromJSON Float
- Data.Aeson.Types: instance [incoherent] FromJSON Int
- Data.Aeson.Types: instance [incoherent] FromJSON Int16
- Data.Aeson.Types: instance [incoherent] FromJSON Int32
- Data.Aeson.Types: instance [incoherent] FromJSON Int64
- Data.Aeson.Types: instance [incoherent] FromJSON Int8
- Data.Aeson.Types: instance [incoherent] FromJSON IntSet
- Data.Aeson.Types: instance [incoherent] FromJSON Integer
- Data.Aeson.Types: instance [incoherent] FromJSON Number
- Data.Aeson.Types: instance [incoherent] FromJSON Text
- Data.Aeson.Types: instance [incoherent] FromJSON UTCTime
- Data.Aeson.Types: instance [incoherent] FromJSON Value
- Data.Aeson.Types: instance [incoherent] FromJSON Word
- Data.Aeson.Types: instance [incoherent] FromJSON Word16
- Data.Aeson.Types: instance [incoherent] FromJSON Word32
- Data.Aeson.Types: instance [incoherent] FromJSON Word64
- Data.Aeson.Types: instance [incoherent] FromJSON Word8
- Data.Aeson.Types: instance [incoherent] FromJSON [Char]
- Data.Aeson.Types: instance [incoherent] FromJSON a => FromJSON (Dual a)
- Data.Aeson.Types: instance [incoherent] FromJSON a => FromJSON (First a)
- Data.Aeson.Types: instance [incoherent] FromJSON a => FromJSON (Last a)
- Data.Aeson.Types: instance [incoherent] FromJSON a => FromJSON (Maybe a)
- Data.Aeson.Types: instance [incoherent] FromJSON a => FromJSON (Vector a)
- Data.Aeson.Types: instance [incoherent] FromJSON a => FromJSON [a]
- Data.Aeson.Types: instance [incoherent] FromJSON v => FromJSON (HashMap ByteString v)
- Data.Aeson.Types: instance [incoherent] FromJSON v => FromJSON (HashMap String v)
- Data.Aeson.Types: instance [incoherent] FromJSON v => FromJSON (HashMap Text v)
- Data.Aeson.Types: instance [incoherent] FromJSON v => FromJSON (Map ByteString v)
- Data.Aeson.Types: instance [incoherent] FromJSON v => FromJSON (Map String v)
- Data.Aeson.Types: instance [incoherent] FromJSON v => FromJSON (Map Text v)
- Data.Aeson.Types: instance [incoherent] Functor Parser
- Data.Aeson.Types: instance [incoherent] Functor Result
- Data.Aeson.Types: instance [incoherent] Hashable Value
- Data.Aeson.Types: instance [incoherent] IsString Value
- Data.Aeson.Types: instance [incoherent] Monad Parser
- Data.Aeson.Types: instance [incoherent] Monad Result
- Data.Aeson.Types: instance [incoherent] MonadPlus Parser
- Data.Aeson.Types: instance [incoherent] MonadPlus Result
- Data.Aeson.Types: instance [incoherent] Monoid (Parser a)
- Data.Aeson.Types: instance [incoherent] Monoid (Result a)
- Data.Aeson.Types: instance [incoherent] NFData Value
- Data.Aeson.Types: instance [incoherent] NFData a => NFData (Result a)
- Data.Aeson.Types: instance [incoherent] Ord DotNetTime
- Data.Aeson.Types: instance [incoherent] Read DotNetTime
- Data.Aeson.Types: instance [incoherent] Show DotNetTime
- Data.Aeson.Types: instance [incoherent] Show Value
- Data.Aeson.Types: instance [incoherent] Show a => Show (Result a)
- Data.Aeson.Types: instance [incoherent] ToJSON ()
- Data.Aeson.Types: instance [incoherent] ToJSON (Ratio Integer)
- Data.Aeson.Types: instance [incoherent] ToJSON Bool
- Data.Aeson.Types: instance [incoherent] ToJSON ByteString
- Data.Aeson.Types: instance [incoherent] ToJSON Char
- Data.Aeson.Types: instance [incoherent] ToJSON DotNetTime
- Data.Aeson.Types: instance [incoherent] ToJSON Double
- Data.Aeson.Types: instance [incoherent] ToJSON Float
- Data.Aeson.Types: instance [incoherent] ToJSON Int
- Data.Aeson.Types: instance [incoherent] ToJSON Int16
- Data.Aeson.Types: instance [incoherent] ToJSON Int32
- Data.Aeson.Types: instance [incoherent] ToJSON Int64
- Data.Aeson.Types: instance [incoherent] ToJSON Int8
- Data.Aeson.Types: instance [incoherent] ToJSON IntSet
- Data.Aeson.Types: instance [incoherent] ToJSON Integer
- Data.Aeson.Types: instance [incoherent] ToJSON Number
- Data.Aeson.Types: instance [incoherent] ToJSON Text
- Data.Aeson.Types: instance [incoherent] ToJSON UTCTime
- Data.Aeson.Types: instance [incoherent] ToJSON Value
- Data.Aeson.Types: instance [incoherent] ToJSON Word
- Data.Aeson.Types: instance [incoherent] ToJSON Word16
- Data.Aeson.Types: instance [incoherent] ToJSON Word32
- Data.Aeson.Types: instance [incoherent] ToJSON Word64
- Data.Aeson.Types: instance [incoherent] ToJSON Word8
- Data.Aeson.Types: instance [incoherent] ToJSON [Char]
- Data.Aeson.Types: instance [incoherent] ToJSON a => ToJSON (Dual a)
- Data.Aeson.Types: instance [incoherent] ToJSON a => ToJSON (First a)
- Data.Aeson.Types: instance [incoherent] ToJSON a => ToJSON (HashSet a)
- Data.Aeson.Types: instance [incoherent] ToJSON a => ToJSON (Last a)
- Data.Aeson.Types: instance [incoherent] ToJSON a => ToJSON (Maybe a)
- Data.Aeson.Types: instance [incoherent] ToJSON a => ToJSON (Set a)
- Data.Aeson.Types: instance [incoherent] ToJSON a => ToJSON (Vector a)
- Data.Aeson.Types: instance [incoherent] ToJSON a => ToJSON [a]
- Data.Aeson.Types: instance [incoherent] ToJSON v => ToJSON (HashMap ByteString v)
- Data.Aeson.Types: instance [incoherent] ToJSON v => ToJSON (HashMap String v)
- Data.Aeson.Types: instance [incoherent] ToJSON v => ToJSON (HashMap Text v)
- Data.Aeson.Types: instance [incoherent] ToJSON v => ToJSON (Map ByteString v)
- Data.Aeson.Types: instance [incoherent] ToJSON v => ToJSON (Map String v)
- Data.Aeson.Types: instance [incoherent] ToJSON v => ToJSON (Map Text v)
- Data.Aeson.Types: instance [incoherent] Typeable DotNetTime
- Data.Aeson.Types: instance [incoherent] Typeable Value
- Data.Aeson.Types: instance [incoherent] Typeable1 Result
+ Data.Aeson: (.!=) :: Parser (Maybe a) -> a -> Parser a
+ Data.Aeson: decode :: FromJSON a => ByteString -> Maybe a
+ Data.Aeson: decode' :: FromJSON a => ByteString -> Maybe a
+ Data.Aeson: json' :: Parser Value
+ Data.Aeson.Generic: decode :: Data a => ByteString -> Maybe a
+ Data.Aeson.Generic: decode' :: Data a => ByteString -> Maybe a
+ Data.Aeson.Generic: encode :: Data a => a -> ByteString
+ Data.Aeson.Parser: json' :: Parser Value
+ Data.Aeson.Parser: value' :: Parser Value
+ Data.Aeson.Types: (.!=) :: Parser (Maybe a) -> a -> Parser a
- Data.Aeson: Array :: Array -> Value
+ Data.Aeson: Array :: !Array -> Value
- Data.Aeson: Number :: Number -> Value
+ Data.Aeson: Number :: !Number -> Value
- Data.Aeson: Object :: Object -> Value
+ Data.Aeson: Object :: !Object -> Value
- Data.Aeson: String :: Text -> Value
+ Data.Aeson: String :: !Text -> Value
- Data.Aeson: type Object = Map Text Value
+ Data.Aeson: type Object = HashMap Text Value
- Data.Aeson.Types: Array :: Array -> Value
+ Data.Aeson.Types: Array :: !Array -> Value
- Data.Aeson.Types: Number :: Number -> Value
+ Data.Aeson.Types: Number :: !Number -> Value
- Data.Aeson.Types: Object :: Object -> Value
+ Data.Aeson.Types: Object :: !Object -> Value
- Data.Aeson.Types: String :: Text -> Value
+ Data.Aeson.Types: String :: !Text -> Value
- Data.Aeson.Types: type Object = Map Text Value
+ Data.Aeson.Types: type Object = HashMap Text Value
Files
- Data/Aeson.hs +34/−7
- Data/Aeson/Encode.hs +3/−3
- Data/Aeson/Functions.hs +33/−20
- Data/Aeson/Generic.hs +347/−9
- Data/Aeson/Parser.hs +41/−131
- Data/Aeson/Parser/Internal.hs +258/−0
- Data/Aeson/TH.hs +287/−130
- Data/Aeson/Types.hs +48/−1133
- Data/Aeson/Types/Class.hs +782/−0
- Data/Aeson/Types/Generic.hs +284/−0
- Data/Aeson/Types/Internal.hs +223/−0
- README.markdown +6/−16
- aeson.cabal +53/−33
- benchmarks/AesonCompareAutoInstances.hs +198/−0
- benchmarks/AesonTuples.hs +45/−0
- benchmarks/Makefile +1/−1
- benchmarks/bench-parse.py +55/−0
- benchmarks/encode.py +14/−0
- examples/Demo.hs +0/−70
- examples/Generic.hs +38/−0
- examples/GenericSYB.hs +30/−0
- examples/Simplest.hs +30/−0
- examples/TemplateHaskell.hs +29/−0
- release-notes.markdown +68/−0
- tests/Makefile +0/−9
- tests/Properties.hs +101/−15
Data/Aeson.hs view
@@ -1,8 +1,9 @@ -- | -- Module: Data.Aeson--- Copyright: (c) 2011 MailRank, Inc.+-- Copyright: (c) 2011 Bryan O'Sullivan+-- (c) 2011 MailRank, Inc. -- License: Apache--- Maintainer: Bryan O'Sullivan <bos@mailrank.com>+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com> -- Stability: experimental -- Portability: portable --@@ -12,8 +13,12 @@ module Data.Aeson (+ -- * Encoding and decoding+ decode+ , decode'+ , encode -- * Core JSON types- Value(..)+ , Value(..) , Array , Object -- * Convenience types@@ -27,12 +32,34 @@ , (.=) , (.:) , (.:?)+ , (.!=) , object- -- * Encoding and parsing- , encode+ -- * Parsing , json+ , json' ) where -import Data.Aeson.Encode-import Data.Aeson.Parser+import Data.Aeson.Encode (encode)+import Data.Aeson.Parser.Internal (decodeWith, json, json') import Data.Aeson.Types+import qualified Data.ByteString.Lazy as L++-- | Efficiently deserialize a JSON value from a lazy 'L.ByteString'.+-- If this fails due to incomplete or invalid input, 'Nothing' is+-- returned.+--+-- This function parses immediately, but defers conversion. See+-- 'json' for details.+decode :: (FromJSON a) => L.ByteString -> Maybe a+decode = decodeWith json fromJSON+{-# INLINE decode #-}++-- | Efficiently deserialize a JSON value from a lazy 'L.ByteString'.+-- If this fails due to incomplete or invalid input, 'Nothing' is+-- returned.+--+-- This function parses and performs conversion immediately. See+-- 'json'' for details.+decode' :: (FromJSON a) => L.ByteString -> Maybe a+decode' = decodeWith json' fromJSON+{-# INLINE decode' #-}
Data/Aeson/Encode.hs view
@@ -4,7 +4,7 @@ -- Module: Data.Aeson.Encode -- Copyright: (c) 2011 MailRank, Inc. -- License: Apache--- Maintainer: Bryan O'Sullivan <bos@mailrank.com>+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com> -- Stability: experimental -- Portability: portable --@@ -25,7 +25,7 @@ import Numeric (showHex) import Blaze.Text (double, integral) import qualified Data.ByteString.Lazy.Char8 as L-import qualified Data.Map as M+import qualified Data.HashMap.Strict as H import qualified Data.Text as T import qualified Data.Vector as V @@ -42,7 +42,7 @@ V.foldr f (fromChar ']') (V.unsafeTail v) where f a z = fromChar ',' `mappend` fromValue a `mappend` z fromValue (Object m) =- case M.toList m of+ case H.toList m of (x:xs) -> fromChar '{' `mappend` one x `mappend` foldr f (fromChar '}') xs
Data/Aeson/Functions.hs view
@@ -1,15 +1,23 @@+-- |+-- Module: Data.Aeson.Functions+-- Copyright: (c) 2011 Bryan O'Sullivan+-- (c) 2011 MailRank, Inc.+-- License: Apache+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>+-- Stability: experimental+-- Portability: portable+ module Data.Aeson.Functions- (- hashMap- , mapHash- , transformMap+ ( mapHashKeyVal+ , hashMapKey+ , mapKeyVal+ , mapKey -- * String conversions , decode , strict , lazy ) where -import Control.Arrow ((***), first) import Data.Hashable (Hashable) import Data.Text (Text) import Data.Text.Encoding (decodeUtf8, encodeUtf8)@@ -18,23 +26,28 @@ import qualified Data.HashMap.Strict as H import qualified Data.Map as M --- | Transform one map into another. The ordering of keys must be--- preserved by the key transformation function.-transformMap :: (Ord k2) => (k1 -> k2) -> (v1 -> v2)- -> M.Map k1 v1 -> M.Map k2 v2-transformMap fk fv = M.fromAscList . map (fk *** fv) . M.toAscList-{-# INLINE transformMap #-}+-- | Transform a 'M.Map' into a 'H.HashMap' while transforming the keys.+mapHashKeyVal :: (Eq k2, Hashable k2) => (k1 -> k2) -> (v1 -> v2)+ -> M.Map k1 v1 -> H.HashMap k2 v2+mapHashKeyVal fk kv = M.foldrWithKey (\k v -> H.insert (fk k) (kv v)) H.empty+{-# INLINE mapHashKeyVal #-} --- | Transform a 'H.HashMap' into a 'M.Map'.-hashMap :: (Ord k2) => (k1 -> k2) -> (v1 -> v2)- -> H.HashMap k1 v1 -> M.Map k2 v2-hashMap fk kv = M.fromList . map (fk *** kv) . H.toList-{-# INLINE hashMap #-}+-- | Transform a 'M.Map' into a 'H.HashMap' while transforming the keys.+hashMapKey :: (Ord k2) => (k1 -> k2)+ -> H.HashMap k1 v -> M.Map k2 v+hashMapKey kv = H.foldrWithKey (M.insert . kv) M.empty+{-# INLINE hashMapKey #-} --- | Transform a 'M.Map' into a 'H.HashMap'.-mapHash :: (Eq k2, Hashable k2) => (k1 -> k2) -> M.Map k1 v -> H.HashMap k2 v-mapHash fk = H.fromList . map (first fk) . M.toList-{-# INLINE mapHash #-}+-- | Transform the keys and values of a 'H.HashMap'.+mapKeyVal :: (Eq k2, Hashable k2) => (k1 -> k2) -> (v1 -> v2)+ -> H.HashMap k1 v1 -> H.HashMap k2 v2+mapKeyVal fk kv = H.foldrWithKey (\k v -> H.insert (fk k) (kv v)) H.empty+{-# INLINE mapKeyVal #-}++-- | Transform the keys of a 'H.HashMap'.+mapKey :: (Eq k2, Hashable k2) => (k1 -> k2) -> H.HashMap k1 v -> H.HashMap k2 v+mapKey fk = mapKeyVal fk id+{-# INLINE mapKey #-} strict :: L.ByteString -> Text strict = decode . B.concat . L.toChunks
Data/Aeson/Generic.hs view
@@ -1,11 +1,12 @@-{-# LANGUAGE PatternGuards, RankNTypes, ScopedTypeVariables #-}+{-# LANGUAGE PatternGuards, Rank2Types, ScopedTypeVariables #-} -- | -- Module: Data.Aeson.Generic--- Copyright: (c) 2011 MailRank, Inc.+-- Copyright: (c) 2011 Bryan O'Sullivan+-- (c) 2011 MailRank, Inc. -- (c) 2008, 2009 Lennart Augustsson -- License: BSD3--- Maintainer: Bryan O'Sullivan <bos@mailrank.com>+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com> -- Stability: experimental -- Portability: portable --@@ -16,15 +17,352 @@ module Data.Aeson.Generic (- fromJSON+ -- * Decoding and encoding+ decode+ , decode'+ , encode+ -- * Lower-level conversion functions+ , fromJSON , toJSON ) where -import Data.Aeson.Types (Value, Result, genericFromJSON, genericToJSON)-import Data.Data (Data)+import Control.Applicative ((<$>))+import Control.Arrow (first)+import Control.Monad.State.Strict+import Data.Aeson.Functions hiding (decode)+import Data.Aeson.Types hiding (FromJSON(..), ToJSON(..), fromJSON)+import Data.Attoparsec.Number (Number)+import Data.Generics+import Data.Hashable (Hashable)+import Data.Int (Int8, Int16, Int32, Int64)+import Data.IntSet (IntSet)+import Data.Maybe (fromJust)+import Data.Text (Text, pack, unpack)+import Data.Text.Encoding (encodeUtf8)+import Data.Time.Clock (UTCTime)+import Data.Word (Word, Word8, Word16, Word32, Word64)+import Data.Aeson.Parser.Internal (decodeWith, json, json')+import qualified Data.Aeson.Encode as E+import qualified Data.Aeson.Functions as F+import qualified Data.Aeson.Types as T+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import qualified Data.HashMap.Strict as H+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Text as DT+import qualified Data.Text.Lazy as LT+import qualified Data.Traversable as T+import qualified Data.Vector as V -fromJSON :: (Data a) => Value -> Result a-fromJSON = genericFromJSON+-- | Efficiently serialize a JSON value as a lazy 'L.ByteString'.+encode :: (Data a) => a -> L.ByteString+encode = E.encode . toJSON+{-# INLINE encode #-} +-- | Efficiently deserialize a JSON value from a lazy 'L.ByteString'.+-- If this fails due to incomplete or invalid input, 'Nothing' is+-- returned.+--+-- This function parses immediately, but defers conversion. See+-- 'json' for details.+decode :: (Data a) => L.ByteString -> Maybe a+decode = decodeWith json fromJSON+{-# INLINE decode #-}++-- | Efficiently deserialize a JSON value from a lazy 'L.ByteString'.+-- If this fails due to incomplete or invalid input, 'Nothing' is+-- returned.+--+-- This function parses and performs conversion immediately. See+-- 'json'' for details.+decode' :: (Data a) => L.ByteString -> Maybe a+decode' = decodeWith json' fromJSON+{-# INLINE decode' #-}++type T a = a -> Value+ toJSON :: (Data a) => a -> Value-toJSON = genericToJSON+toJSON = toJSON_generic+ `ext1Q` list+ `ext1Q` vector+ `ext1Q` set+ `ext2Q'` mapAny+ `ext2Q'` hashMapAny+ -- Use the standard encoding for all base types.+ `extQ` (T.toJSON :: T Integer)+ `extQ` (T.toJSON :: T Int)+ `extQ` (T.toJSON :: T Int8)+ `extQ` (T.toJSON :: T Int16)+ `extQ` (T.toJSON :: T Int32)+ `extQ` (T.toJSON :: T Int64)+ `extQ` (T.toJSON :: T Word)+ `extQ` (T.toJSON :: T Word8)+ `extQ` (T.toJSON :: T Word16)+ `extQ` (T.toJSON :: T Word32)+ `extQ` (T.toJSON :: T Word64)+ `extQ` (T.toJSON :: T Double)+ `extQ` (T.toJSON :: T Number)+ `extQ` (T.toJSON :: T Float)+ `extQ` (T.toJSON :: T Rational)+ `extQ` (T.toJSON :: T Char)+ `extQ` (T.toJSON :: T Text)+ `extQ` (T.toJSON :: T LT.Text)+ `extQ` (T.toJSON :: T String)+ `extQ` (T.toJSON :: T B.ByteString)+ `extQ` (T.toJSON :: T L.ByteString)+ `extQ` (T.toJSON :: T T.Value)+ `extQ` (T.toJSON :: T DotNetTime)+ `extQ` (T.toJSON :: T UTCTime)+ `extQ` (T.toJSON :: T IntSet)+ `extQ` (T.toJSON :: T Bool)+ `extQ` (T.toJSON :: T ())+ --`extQ` (T.toJSON :: T Ordering)+ where+ list xs = Array . V.fromList . map toJSON $ xs+ vector v = Array . V.map toJSON $ v+ set s = Array . V.fromList . map toJSON . Set.toList $ s++ mapAny m+ | tyrep == typeOf DT.empty = remap id+ | tyrep == typeOf LT.empty = remap LT.toStrict+ | tyrep == typeOf "" = remap pack+ | tyrep == typeOf B.empty = remap F.decode+ | tyrep == typeOf L.empty = remap strict+ | otherwise = modError "toJSON" $+ "cannot convert map keyed by type " ++ show tyrep+ where tyrep = typeOf . head . Map.keys $ m+ remap f = Object . mapHashKeyVal (f . fromJust . cast) toJSON $ m++ hashMapAny m+ | tyrep == typeOf DT.empty = remap id+ | tyrep == typeOf LT.empty = remap LT.toStrict+ | tyrep == typeOf "" = remap pack+ | tyrep == typeOf B.empty = remap F.decode+ | tyrep == typeOf L.empty = remap strict+ | otherwise = modError "toJSON" $+ "cannot convert map keyed by type " ++ show tyrep+ where tyrep = typeOf . head . H.keys $ m+ remap f = Object . mapKeyVal (f . fromJust . cast) toJSON $ m++-- Skip leading '_' in field name so we can use keywords+-- etc. as field names.+mungeField :: String -> Text+mungeField ('_':cs) = pack cs+mungeField cs = pack cs++toJSON_generic :: (Data a) => a -> Value+toJSON_generic = generic+ where+ -- Generic encoding of an algebraic data type.+ generic a =+ case dataTypeRep (dataTypeOf a) of+ -- No constructor, so it must be an error value. Code+ -- it anyway as Null.+ AlgRep [] -> Null+ -- Elide a single constructor and just code the arguments.+ AlgRep [c] -> encodeArgs c (gmapQ toJSON a)+ -- For multiple constructors, make an object with a+ -- field name that is the constructor (except lower+ -- case) and the data is the arguments encoded.+ AlgRep _ -> encodeConstr (toConstr a) (gmapQ toJSON a)+ rep -> err (dataTypeOf a) rep+ where+ err dt r = modError "toJSON" $ "not AlgRep " +++ show r ++ "(" ++ show dt ++ ")"+ -- Encode nullary constructor as a string.+ -- Encode non-nullary constructors as an object with the constructor+ -- name as the single field and the arguments as the value.+ -- Use an array if the are no field names, but elide singleton arrays,+ -- and use an object if there are field names.+ encodeConstr c [] = String . constrString $ c+ encodeConstr c as = object [(constrString c, encodeArgs c as)]++ constrString = pack . showConstr++ encodeArgs c = encodeArgs' (constrFields c)+ encodeArgs' [] [j] = j+ encodeArgs' [] js = Array . V.fromList $ js+ encodeArgs' ns js = object $ zip (map mungeField ns) js+++fromJSON :: (Data a) => Value -> Result a+fromJSON = parse parseJSON++type F a = Parser a++parseJSON :: (Data a) => Value -> Parser a+parseJSON j = parseJSON_generic j+ `ext1R` list+ `ext1R` vector+ `ext2R'` mapAny+ `ext2R'` hashMapAny+ -- Use the standard encoding for all base types.+ `extR` (value :: F Integer)+ `extR` (value :: F Int)+ `extR` (value :: F Int8)+ `extR` (value :: F Int16)+ `extR` (value :: F Int32)+ `extR` (value :: F Int64)+ `extR` (value :: F Word)+ `extR` (value :: F Word8)+ `extR` (value :: F Word16)+ `extR` (value :: F Word32)+ `extR` (value :: F Word64)+ `extR` (value :: F Double)+ `extR` (value :: F Number)+ `extR` (value :: F Float)+ `extR` (value :: F Rational)+ `extR` (value :: F Char)+ `extR` (value :: F Text)+ `extR` (value :: F LT.Text)+ `extR` (value :: F String)+ `extR` (value :: F B.ByteString)+ `extR` (value :: F L.ByteString)+ `extR` (value :: F T.Value)+ `extR` (value :: F DotNetTime)+ `extR` (value :: F UTCTime)+ `extR` (value :: F IntSet)+ `extR` (value :: F Bool)+ `extR` (value :: F ())+ where+ value :: (T.FromJSON a) => Parser a+ value = T.parseJSON j+ list :: (Data a) => Parser [a]+ list = V.toList <$> parseJSON j+ vector :: (Data a) => Parser (V.Vector a)+ vector = case j of+ Array js -> V.mapM parseJSON js+ _ -> myFail++ mapAny :: forall e f. (Data e, Data f) => Parser (Map.Map f e)+ mapAny+ | tyrep == typeOf DT.empty = process id+ | tyrep == typeOf LT.empty = process LT.fromStrict+ | tyrep == typeOf "" = process DT.unpack+ | tyrep == typeOf B.empty = process encodeUtf8+ | tyrep == typeOf L.empty = process lazy+ | otherwise = myFail+ where+ process f = maybe myFail return . cast =<< parseWith f+ parseWith :: (Ord c) => (Text -> c) -> Parser (Map.Map c e)+ parseWith f = case j of+ Object js -> Map.fromList . map (first f) . H.toList <$>+ T.mapM parseJSON js+ _ -> myFail+ tyrep = typeOf (undefined :: f)++ hashMapAny :: forall e f. (Data e, Data f) => Parser (H.HashMap f e)+ hashMapAny+ | tyrep == typeOf DT.empty = process id+ | tyrep == typeOf LT.empty = process LT.fromStrict+ | tyrep == typeOf "" = process DT.unpack+ | tyrep == typeOf B.empty = process encodeUtf8+ | tyrep == typeOf L.empty = process lazy+ | otherwise = myFail+ where+ process f = maybe myFail return . cast =<< parseWith f+ parseWith :: (Eq c, Hashable c) => (Text -> c) -> Parser (H.HashMap c e)+ parseWith f = case j of+ Object js -> mapKey f <$> T.mapM parseJSON js+ _ -> myFail+ tyrep = typeOf (undefined :: f)++ myFail = modFail "parseJSON" $ "bad data: " ++ show j++parseJSON_generic :: (Data a) => Value -> Parser a+parseJSON_generic j = generic+ where+ typ = dataTypeOf $ resType generic+ generic = case dataTypeRep typ of+ AlgRep [] -> case j of+ Null -> return (modError "parseJSON" "empty type")+ _ -> modFail "parseJSON" "no-constr bad data"+ AlgRep [_] -> decodeArgs (indexConstr typ 1) j+ AlgRep _ -> do (c, j') <- getConstr typ j; decodeArgs c j'+ rep -> modFail "parseJSON" $+ show rep ++ "(" ++ show typ ++ ")"+ getConstr t (Object o) | [(s, j')] <- fromJSObject o = do+ c <- readConstr' t s+ return (c, j')+ getConstr t (String js) = do c <- readConstr' t (unpack js)+ return (c, Null) -- handle nullary ctor+ getConstr _ _ = modFail "parseJSON" "bad constructor encoding"+ readConstr' t s =+ maybe (modFail "parseJSON" $ "unknown constructor: " ++ s ++ " " +++ show t)+ return $ readConstr t s++ decodeArgs c0 = go (numConstrArgs (resType generic) c0) c0+ (constrFields c0)+ where+ go 0 c _ Null = construct c [] -- nullary constructor+ go 1 c [] jd = construct c [jd] -- unary constructor+ go n c [] (Array js)+ | n > 1 = construct c (V.toList js) -- no field names+ -- FIXME? We could allow reading an array into a constructor+ -- with field names.+ go _ c fs@(_:_) (Object o) = selectFields o fs >>=+ construct c -- field names+ go _ c _ jd = modFail "parseJSON" $+ "bad decodeArgs data " ++ show (c, jd)++ fromJSObject = map (first unpack) . H.toList++ -- Build the value by stepping through the list of subparts.+ construct c = evalStateT $ fromConstrM f c+ where f :: (Data a) => StateT [Value] Parser a+ f = do js <- get+ case js of+ [] -> lift $ modFail "construct" "empty list"+ (j':js') -> do put js'; lift $ parseJSON j'++ -- Select the named fields from a JSON object.+ selectFields fjs = mapM sel+ where sel f = maybe (modFail "parseJSON" $ "field does not exist " +++ f) return $ H.lookup (mungeField f) fjs++ -- Count how many arguments a constructor has. The value x is+ -- used to determine what type the constructor returns.+ numConstrArgs :: (Data a) => a -> Constr -> Int+ numConstrArgs x c = execState (fromConstrM f c `asTypeOf` return x) 0+ where f = do modify (+1); return undefined++ resType :: MonadPlus m => m a -> a+ resType _ = modError "parseJSON" "resType"++modFail :: (Monad m) => String -> String -> m a+modFail func err = fail $ "Data.Aeson.Generic." ++ func ++ ": " ++ err++modError :: String -> String -> a+modError func err = error $ "Data.Aeson.Generic." ++ func ++ ": " ++ err+++-- Type extension for binary type constructors.++-- | Flexible type extension+ext2' :: (Data a, Typeable2 t)+ => c a+ -> (forall d1 d2. (Data d1, Data d2) => c (t d1 d2))+ -> c a+ext2' def ext = maybe def id (dataCast2 ext)++-- | Type extension of queries for type constructors+ext2Q' :: (Data d, Typeable2 t)+ => (d -> q)+ -> (forall d1 d2. (Data d1, Data d2) => t d1 d2 -> q)+ -> d -> q+ext2Q' def ext = unQ ((Q def) `ext2'` (Q ext))++-- | Type extension of readers for type constructors+ext2R' :: (Monad m, Data d, Typeable2 t)+ => m d+ -> (forall d1 d2. (Data d1, Data d2) => m (t d1 d2))+ -> m d+ext2R' def ext = unR ((R def) `ext2'` (R ext))++-- | The type constructor for queries+newtype Q q x = Q { unQ :: x -> q }++-- | The type constructor for readers+newtype R m x = R { unR :: m x }
Data/Aeson/Parser.hs view
@@ -4,149 +4,59 @@ -- Module: Data.Aeson.Parser -- Copyright: (c) 2011 MailRank, Inc. -- License: Apache--- Maintainer: Bryan O'Sullivan <bos@mailrank.com>+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com> -- Stability: experimental -- Portability: portable -- -- Efficiently and correctly parse a JSON string. The string must be -- encoded as UTF-8.+--+-- It can be useful to think of parsing as occurring in two phases:+--+-- * Identification of the textual boundaries of a JSON value. This+-- is always strict, so that an invalid JSON document can be+-- rejected as soon as possible.+--+-- * Conversion of a JSON value to a Haskell value. This may be+-- either immediate (strict) or deferred (lazy); see below for+-- details.+--+-- The question of whether to choose a lazy or strict parser is+-- subtle, but it can have significant performance implications,+-- resulting in changes in CPU use and memory footprint of 30% to 50%,+-- or occasionally more. Measure the performance of your application+-- with each! module Data.Aeson.Parser (+ -- * Lazy parsers+ -- $lazy json , value , jstring+ -- * Strict parsers+ -- $strict+ , json'+ , value' ) where -import Blaze.ByteString.Builder (fromByteString, toByteString)-import Blaze.ByteString.Builder.Char.Utf8 (fromChar)-import Blaze.ByteString.Builder.Word (fromWord8)-import Control.Applicative as A-import Data.Aeson.Types (Value(..))-import Data.Attoparsec.Char8-import Data.Bits ((.|.), shiftL)-import Data.ByteString as B-import Data.Char (chr)-import Data.Map as Map-import Data.Monoid (mappend, mempty)-import Data.Text as T-import Data.Text.Encoding (decodeUtf8)-import Data.Vector as Vector hiding ((++))-import Data.Word (Word8)-import qualified Data.Attoparsec as A-import qualified Data.Attoparsec.Zepto as Z-import qualified Data.ByteString.Char8 as B8-import qualified Data.ByteString.Unsafe as B---- | Parse a top-level JSON value. This must be either an object or--- an array.-json :: Parser Value-json = do- c <- skipSpace *> satisfy (`B8.elem` "{[")- if c == '{'- then object_- else array_--object_ :: Parser Value-object_ = {-# SCC "object_" #-} do- skipSpace- let pair = do- a <- jstring <* skipSpace- b <- char ':' *> skipSpace *> value- return (a,b)- vals <- ((pair <* skipSpace) `sepBy` (char ',' *> skipSpace)) <* char '}'- return . Object $ Map.fromList vals--array_ :: Parser Value-array_ = {-# SCC "array_" #-} do- skipSpace- vals <- ((value <* skipSpace) `sepBy` (char ',' *> skipSpace)) <* char ']'- return . Array $ Vector.fromList vals---- | Parse any JSON value. Use 'json' in preference to this function--- if you are parsing data from an untrusted source.-value :: Parser Value-value = most <|> (Number <$> number)- where- most = do- c <- satisfy (`B8.elem` "{[\"ftn")- case c of- '{' -> object_- '[' -> array_- '"' -> String <$> jstring_- 'f' -> string "alse" *> pure (Bool False)- 't' -> string "rue" *> pure (Bool True)- 'n' -> string "ull" *> pure Null- _ -> error "attoparsec panic! the impossible happened!"--doubleQuote, backslash :: Word8-doubleQuote = 34-backslash = 92-{-# INLINE backslash #-}-{-# INLINE doubleQuote #-}--jstring :: Parser Text-jstring = A.word8 doubleQuote *> jstring_---- | Parse a string without a leading quote.-jstring_ :: Parser Text-jstring_ = {-# SCC "jstring_" #-} do- s <- A.scan False $ \s c -> if s then Just False- else if c == doubleQuote- then Nothing- else Just (c == backslash)- _ <- A.word8 doubleQuote- if backslash `B.elem` s- then case Z.parse unescape s of- Right r -> return (decodeUtf8 r)- Left err -> fail err- else return (decodeUtf8 s)-{-# INLINE jstring_ #-}+import Data.Aeson.Parser.Internal (json, json', jstring, value, value') -unescape :: Z.Parser ByteString-unescape = toByteString <$> go mempty where- go acc = do- h <- Z.takeWhile (/=backslash)- let rest = do- start <- Z.take 2- let !slash = B.unsafeHead start- !t = B.unsafeIndex start 1- escape = case B.findIndex (==t) "\"\\/ntbrfu" of- Just i -> i- _ -> 255- if slash /= backslash || escape == 255- then fail "invalid JSON escape sequence"- else do- let cont m = go (acc `mappend` fromByteString h `mappend` m)- {-# INLINE cont #-}- if t /= 117 -- 'u'- then cont (fromWord8 (B.unsafeIndex mapping escape))- else do- a <- hexQuad- if a < 0xd800 || a > 0xdfff- then cont (fromChar (chr a))- else do- b <- Z.string "\\u" *> hexQuad- if a <= 0xdbff && b >= 0xdc00 && b <= 0xdfff- then let !c = ((a - 0xd800) `shiftL` 10) +- (b - 0xdc00) + 0x10000- in cont (fromChar (chr c))- else fail "invalid UTF-16 surrogates"- done <- Z.atEnd- if done- then return (acc `mappend` fromByteString h)- else rest- mapping = "\"\\/\n\t\b\r\f"+-- $lazy+--+-- The 'json' and 'value' parsers decouple identification from+-- conversion. Identification occurs immediately (so that an invalid+-- JSON document can be rejected as early as possible), but conversion+-- to a Haskell value is deferred until that value is needed.+--+-- This decoupling can be time-efficient if only a smallish subset of+-- elements in a JSON value need to be inspected, since the cost of+-- conversion is zero for uninspected elements. The trade off is an+-- increase in memory usage, due to allocation of thunks for values+-- that have not yet been converted. -hexQuad :: Z.Parser Int-hexQuad = do- s <- Z.take 4- let hex n | w >= 48 && w <= 57 = w - 48- | w >= 97 && w <= 122 = w - 87- | w >= 65 && w <= 90 = w - 55- | otherwise = 255- where w = fromIntegral $ B.unsafeIndex s n- a = hex 0; b = hex 1; c = hex 2; d = hex 3- if (a .|. b .|. c .|. d) /= 255- then return $! d .|. (c `shiftL` 4) .|. (b `shiftL` 8) .|. (a `shiftL` 12)- else fail "invalid hex escape"+-- $strict+--+-- The 'json'' and 'value'' parsers combine identification with+-- conversion. They consume more CPU cycles up front, but have a+-- smaller memory footprint.
+ Data/Aeson/Parser/Internal.hs view
@@ -0,0 +1,258 @@+{-# LANGUAGE BangPatterns, OverloadedStrings #-}++-- |+-- Module: Data.Aeson.Parser.Internal+-- Copyright: (c) 2011 Bryan O'Sullivan+-- (c) 2011 MailRank, Inc.+-- License: Apache+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>+-- Stability: experimental+-- Portability: portable+--+-- Efficiently and correctly parse a JSON string. The string must be+-- encoded as UTF-8.++module Data.Aeson.Parser.Internal+ (+ -- * Lazy parsers+ json+ , value+ , jstring+ -- * Strict parsers+ , json'+ , value'+ -- * Helpers+ , decodeWith+ ) where++import Blaze.ByteString.Builder (fromByteString, toByteString)+import Blaze.ByteString.Builder.Char.Utf8 (fromChar)+import Blaze.ByteString.Builder.Word (fromWord8)+import Control.Applicative as A+import Data.Aeson.Types (Result(..), Value(..))+import Data.Attoparsec.Char8 hiding (Result)+import Data.Bits ((.|.), shiftL)+import Data.ByteString as B+import Data.Char (chr)+import Data.Monoid (mappend, mempty)+import Data.Text as T+import Data.Text.Encoding (decodeUtf8)+import Data.Vector as Vector hiding ((++))+import Data.Word (Word8)+import qualified Data.Attoparsec as A+import qualified Data.Attoparsec.Lazy as L+import qualified Data.Attoparsec.Zepto as Z+import qualified Data.ByteString.Char8 as B8+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Unsafe as B+import qualified Data.HashMap.Strict as H++-- | Parse a top-level JSON value. This must be either an object or+-- an array, per RFC 4627.+--+-- The conversion of a parsed value to a Haskell value is deferred+-- until the Haskell value is needed. This may improve performance if+-- only a subset of the results of conversions are needed, but at a+-- cost in thunk allocation.+json :: Parser Value+json = json_ object_ array_++-- | Parse a top-level JSON value. This must be either an object or+-- an array, per RFC 4627.+--+-- This is a strict version of 'json' which avoids building up thunks+-- during parsing; it performs all conversions immediately. Prefer+-- this version if most of the JSON data needs to be accessed.+json' :: Parser Value+json' = json_ object_' array_'++json_ :: Parser Value -> Parser Value -> Parser Value+json_ obj ary = do+ w <- skipSpace *> A.satisfy (\w -> w == 123 || w == 91)+ if w == 123+ then obj+ else ary+{-# INLINE json_ #-}++object_ :: Parser Value+object_ = {-# SCC "object_" #-} Object <$> objectValues jstring value++object_' :: Parser Value+object_' = {-# SCC "object_'" #-} do+ !vals <- objectValues jstring' value'+ return (Object vals)+ where+ jstring' = do+ !s <- jstring+ return s++objectValues :: Parser Text -> Parser Value -> Parser (H.HashMap Text Value)+objectValues str val = do+ skipSpace+ let pair = do+ a <- str <* skipSpace+ b <- char ':' *> skipSpace *> val+ return (a,b)+ vals <- ((pair <* skipSpace) `sepBy` (char ',' *> skipSpace)) <* char '}'+ return (H.fromList vals)+{-# INLINE objectValues #-}++array_ :: Parser Value+array_ = {-# SCC "array_" #-} Array <$> arrayValues value++array_' :: Parser Value+array_' = {-# SCC "array_'" #-} do+ !vals <- arrayValues value'+ return (Array vals)++arrayValues :: Parser Value -> Parser (Vector Value)+arrayValues val = do+ skipSpace+ vals <- ((val <* skipSpace) `sepBy` (char ',' *> skipSpace)) <* char ']'+ return (Vector.fromList vals)+{-# INLINE arrayValues #-}++-- | Parse any JSON value. You should usually 'json' in preference to+-- this function, as this function relaxes the object-or-array+-- requirement of RFC 4627.+--+-- In particular, be careful in using this function if you think your+-- code might interoperate with Javascript. A naïve Javascript+-- library that parses JSON data using @eval@ is vulnerable to attack+-- unless the encoded data represents an object or an array. JSON+-- implementations in other languages conform to that same restriction+-- to preserve interoperability and security.+value :: Parser Value+value = most <|> (Number <$> number)+ where+ most = do+ c <- satisfy (`B8.elem` "{[\"ftn")+ case c of+ '{' -> object_+ '[' -> array_+ '"' -> String <$> jstring_+ 'f' -> string "alse" *> pure (Bool False)+ 't' -> string "rue" *> pure (Bool True)+ 'n' -> string "ull" *> pure Null+ _ -> error "attoparsec panic! the impossible happened!"++-- | Strict version of 'value'. See also 'json''.+value' :: Parser Value+value' = most <|> num+ where+ most = do+ c <- satisfy (`B8.elem` "{[\"ftn")+ case c of+ '{' -> object_'+ '[' -> array_'+ '"' -> do+ !s <- jstring_+ return (String s)+ 'f' -> string "alse" *> pure (Bool False)+ 't' -> string "rue" *> pure (Bool True)+ 'n' -> string "ull" *> pure Null+ _ -> error "attoparsec panic! the impossible happened!"+ num = do+ !n <- number+ return (Number n)++doubleQuote, backslash :: Word8+doubleQuote = 34+backslash = 92+{-# INLINE backslash #-}+{-# INLINE doubleQuote #-}++-- | Parse a quoted JSON string.+jstring :: Parser Text+jstring = A.word8 doubleQuote *> jstring_++-- | Parse a string without a leading quote.+jstring_ :: Parser Text+jstring_ = {-# SCC "jstring_" #-} do+ s <- A.scan False $ \s c -> if s then Just False+ else if c == doubleQuote+ then Nothing+ else Just (c == backslash)+ _ <- A.word8 doubleQuote+ if backslash `B.elem` s+ then case Z.parse unescape s of+ Right r -> return (decodeUtf8 r)+ Left err -> fail err+ else return (decodeUtf8 s)+{-# INLINE jstring_ #-}++unescape :: Z.Parser ByteString+unescape = toByteString <$> go mempty where+ go acc = do+ h <- Z.takeWhile (/=backslash)+ let rest = do+ start <- Z.take 2+ let !slash = B.unsafeHead start+ !t = B.unsafeIndex start 1+ escape = case B.findIndex (==t) "\"\\/ntbrfu" of+ Just i -> i+ _ -> 255+ if slash /= backslash || escape == 255+ then fail "invalid JSON escape sequence"+ else do+ let cont m = go (acc `mappend` fromByteString h `mappend` m)+ {-# INLINE cont #-}+ if t /= 117 -- 'u'+ then cont (fromWord8 (B.unsafeIndex mapping escape))+ else do+ a <- hexQuad+ if a < 0xd800 || a > 0xdfff+ then cont (fromChar (chr a))+ else do+ b <- Z.string "\\u" *> hexQuad+ if a <= 0xdbff && b >= 0xdc00 && b <= 0xdfff+ then let !c = ((a - 0xd800) `shiftL` 10) ++ (b - 0xdc00) + 0x10000+ in cont (fromChar (chr c))+ else fail "invalid UTF-16 surrogates"+ done <- Z.atEnd+ if done+ then return (acc `mappend` fromByteString h)+ else rest+ mapping = "\"\\/\n\t\b\r\f"++hexQuad :: Z.Parser Int+hexQuad = do+ s <- Z.take 4+ let hex n | w >= 48 && w <= 57 = w - 48+ | w >= 97 && w <= 122 = w - 87+ | w >= 65 && w <= 90 = w - 55+ | otherwise = 255+ where w = fromIntegral $ B.unsafeIndex s n+ a = hex 0; b = hex 1; c = hex 2; d = hex 3+ if (a .|. b .|. c .|. d) /= 255+ then return $! d .|. (c `shiftL` 4) .|. (b `shiftL` 8) .|. (a `shiftL` 12)+ else fail "invalid hex escape"++decodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString -> Maybe a+decodeWith p to s =+ case L.parse p s of+ L.Done _ v -> case to v of+ Success a -> Just a+ _ -> Nothing+ _ -> Nothing+{-# INLINE decodeWith #-}++-- $lazy+--+-- The 'json' and 'value' parsers decouple identification from+-- conversion. Identification occurs immediately (so that an invalid+-- JSON document can be rejected as early as possible), but conversion+-- to a Haskell value is deferred until that value is needed.+--+-- This decoupling can be time-efficient if only a smallish subset of+-- elements in a JSON value need to be inspected, since the cost of+-- conversion is zero for uninspected elements. The trade off is an+-- increase in memory usage, due to allocation of thunks for values+-- that have not yet been converted.++-- $strict+--+-- The 'json'' and 'value'' parsers combine identification with+-- conversion. They consume more CPU cycles up front, but have a+-- smaller memory footprint.
Data/Aeson/TH.hs view
@@ -2,6 +2,8 @@ {-| Module: Data.Aeson.TH+Copyright: (c) 2011 Bryan O'Sullivan+ (c) 2011 MailRank, Inc. License: Apache Stability: experimental Portability: portable@@ -38,8 +40,8 @@ import Control.Monad import Data.Aeson import Data.Aeson.TH-import qualified Data.Map as M-import qualified Data.Text as T+import qualified Data.HashMap.Strict as H+import qualified Data.Text as T import qualified Data.Vector as V instance 'ToJSON' a => 'ToJSON' (D a) where@@ -47,21 +49,23 @@ \value -> case value of Nullary ->- 'object' ['T.pack' \"Nullary\" .= 'toJSON' ([] :: [()])]+ 'object' [T.pack \"Nullary\" .= 'toJSON' ([] :: [()])] Unary arg1 ->- 'object' ['T.pack' \"Unary\" .= 'toJSON' arg1]+ 'object' [T.pack \"Unary\" .= 'toJSON' arg1] Product arg1 arg2 arg3 ->- 'object' [ 'T.pack' \"Product\"- .= 'toJSON' [ 'toJSON' arg1- , 'toJSON' arg2- , 'toJSON' arg3- ]+ 'object' [ T.pack \"Product\"+ .= ('Array' $ 'V.create' $ do+ mv <- 'VM.unsafeNew' 3+ 'VM.unsafeWrite' mv 0 ('toJSON' arg1)+ 'VM.unsafeWrite' mv 1 ('toJSON' arg2)+ 'VM.unsafeWrite' mv 2 ('toJSON' arg3)+ return mv) ] Record arg1 arg2 arg3 ->- 'object' [ 'T.pack' \"Record\"- .= 'object' [ 'T.pack' \"One\" '.=' arg1- , 'T.pack' \"Two\" '.=' arg2- , 'T.pack' \"Three\" '.=' arg3+ 'object' [ T.pack \"Record\"+ .= 'object' [ T.pack \"One\" '.=' arg1+ , T.pack \"Two\" '.=' arg2+ , T.pack \"Three\" '.=' arg3 ] ] @@@ -72,35 +76,45 @@ \value -> case value of 'Object' obj ->- case 'M.toList' obj of+ case H.toList obj of [(conKey, conVal)] ->- case conKey of- _ | (conKey '==' 'T.pack' \"Nullary\") ->- case conVal of- 'Array' arr | 'V.null' arr -> 'pure' Nullary- _ -> 'mzero'- | (conKey '==' 'T.pack' \"Unary\") ->- case conVal of- arg -> Unary '<$>' 'parseJSON' arg- | (conKey '==' 'T.pack' \"Product\") ->- case conVal of- 'Array' arr | 'V.length' arr '==' 3 ->- 'Product' '<$>' 'parseJSON' (arr 'V.!' 0)- '<*>' 'parseJSON' (arr 'V.!' 1)- '<*>' 'parseJSON' (arr 'V.!' 2)- _ -> 'mzero'- | (conKey '==' 'T.pack' \"Record\") ->- case conVal of- 'Object' obj ->- Record '<$>' (obj '.:' 'T.pack' \"One\")- '<*>' (obj '.:' 'T.pack' \"Two\")- '<*>' (obj '.:' 'T.pack' \"Three\")- _ -> 'mzero'- | 'otherwise' -> 'mzero'- _ -> 'mzero'- _ -> 'mzero'+ case conKey of+ _ | conKey == T.pack \"Nullary\" ->+ case conVal of+ 'Array' arr ->+ if V.null arr+ then pure Nullary+ else fail \"\<error message\>\"+ _ -> fail \"\<error message\>\"+ | conKey == T.pack \"Unary\" ->+ case conVal of+ arg -> Unary \<$\> parseJSON arg+ | conKey == T.pack \"Product\" ->+ case conVal of+ 'Array' arr ->+ if V.length arr == 3+ then Product \<$\> 'parseJSON' (arr `V.unsafeIndex` 0)+ \<*\> 'parseJSON' (arr `V.unsafeIndex` 1)+ \<*\> 'parseJSON' (arr `V.unsafeIndex` 2)+ else fail \"\<error message\>\"+ _ -> fail \"\<error message\>\"+ | conKey == T.pack \"Record\" ->+ case conVal of+ 'Object' recObj ->+ if H.size recObj == 3+ then Record \<$\> recObj '.:' T.pack \"One\"+ \<*\> recObj '.:' T.pack \"Two\"+ \<*\> recObj '.:' T.pack \"Three\"+ else fail \"\<error message\>\"+ _ -> fail \"\<error message\>\"+ | otherwise -> fail \"\<error message\>\"+ _ -> fail \"\<error message\>\"+ _ -> fail \"\<error message\>\" @ +Note that every \"\<error message\>\" is in fact a descriptive message which+provides as much information as is reasonable about the failed parse.+ Now we can use the newly created instances. @@@ -114,6 +128,13 @@ >>> fromJSON (toJSON d) == Success d > True +Please note that you can derive instances for tuples using the following syntax:++@+-- FromJSON and ToJSON instances for 4-tuples.+$('deriveJSON' id ''(,,,))+@+ -} module Data.Aeson.TH@@ -131,34 +152,38 @@ -------------------------------------------------------------------------------- -- from aeson:-import Data.Aeson ( toJSON, object, (.=), (.:)+import Data.Aeson ( toJSON, Object, object, (.=) , ToJSON, toJSON , FromJSON, parseJSON )-import Data.Aeson.Types ( Value(..) )+import Data.Aeson.Types ( Value(..), Parser ) -- from base: import Control.Applicative ( pure, (<$>), (<*>) )-import Control.Monad ( return, mapM, mzero, liftM2 )+import Control.Monad ( return, mapM, liftM2, fail ) import Data.Bool ( otherwise ) import Data.Eq ( (==) ) import Data.Function ( ($), (.), id ) import Data.Functor ( fmap )-import Data.List ( (++), foldl', map, zip, genericLength )-import Prelude ( String, (-), Integer, error )+import Data.List ( (++), foldl, foldl', intercalate+ , length, map, zip, genericLength+ )+import Data.Maybe ( Maybe(Nothing, Just) )+import Prelude ( String, (-), Integer, fromIntegral, error )+import Text.Printf ( printf ) import Text.Show ( show ) #if __GLASGOW_HASKELL__ < 700-import Control.Monad ( (>>=), fail )+import Control.Monad ( (>>=) ) import Prelude ( fromInteger ) #endif--- from containers:-import qualified Data.Map as M ( toList )+-- from unordered-containers:+import qualified Data.HashMap.Strict as H ( lookup, toList, size ) -- from template-haskell: import Language.Haskell.TH -- from text:-import qualified Data.Text as T ( pack )+import qualified Data.Text as T ( Text, pack, unpack ) -- from vector:-import qualified Data.Vector as V ( (!), null, length )-+import qualified Data.Vector as V ( unsafeIndex, null, length, create )+import qualified Data.Vector.Mutable as VM ( unsafeNew, unsafeWrite ) --------------------------------------------------------------------------------@@ -210,7 +235,11 @@ -- instance 'ToJSON' Foo where -- 'toJSON' = -- \value -> case value of--- Foo arg1 arg2 -> 'toJSON' ['toJSON' arg1, 'toJSON' arg2]+-- Foo arg1 arg2 -> 'Array' $ 'V.create' $ do+-- mv <- 'VM.unsafeNew' 2+-- 'VM.unsafeWrite' mv 0 ('toJSON' arg1)+-- 'VM.unsafeWrite' mv 1 ('toJSON' arg2)+-- return mv -- @ deriveToJSON :: (String -> String) -- ^ Function to change field names.@@ -241,12 +270,12 @@ -- Example: -- -- @--- data Foo = Foo 'Int'+-- data Foo = Foo Int -- @ -- -- @ -- encodeFoo :: Foo -> 'Value'--- encodeFoo = $('mkToJSON' 'id' ''Foo)+-- encodeFoo = $('mkToJSON' id ''Foo) -- @ -- -- This will splice in the following code:@@ -306,12 +335,28 @@ [] -- Polyadic constructors with special case for unary constructors. encodeArgs withExp _ (NormalC conName ts) = do- args <- mapM newName ["arg" ++ show n | (_, n) <- zip ts [1 :: Integer ..]]- let js = case [[e|toJSON|] `appE` varE arg | arg <- args] of- -- Single argument is directly converted.- [e] -> e- -- Multiple arguments are converted to a JSON array.- es -> [e|toJSON|] `appE` listE es+ let len = length ts+ args <- mapM newName ["arg" ++ show n | n <- [1..len]]+ js <- case [[e|toJSON|] `appE` varE arg | arg <- args] of+ -- Single argument is directly converted.+ [e] -> return e+ -- Multiple arguments are converted to a JSON array.+ es -> do+ mv <- newName "mv"+ let newMV = bindS (varP mv)+ ([e|VM.unsafeNew|] `appE`+ litE (integerL $ fromIntegral len))+ stmts = [ noBindS $+ [e|VM.unsafeWrite|] `appE`+ (varE mv) `appE`+ litE (integerL ix) `appE`+ e+ | (ix, e) <- zip [(0::Integer)..] es+ ]+ ret = noBindS $ [e|return|] `appE` varE mv+ return $ [e|Array|] `appE`+ (varE 'V.create `appE`+ doE (newMV:stmts++[ret])) match (conP conName $ map varP args) (normalB $ withExp js) []@@ -352,8 +397,8 @@ -- Example: -- -- @--- data Foo = Foo 'Char' 'Int'--- $('deriveFromJSON' 'id' ''Foo)+-- data Foo = Foo Char Int+-- $('deriveFromJSON' id ''Foo) -- @ -- -- This will splice in the following code:@@ -362,10 +407,12 @@ -- instance 'FromJSON' Foo where -- 'parseJSON' = -- \value -> case value of--- 'Array' arr | ('V.length' arr '==' 2) ->--- Foo '<$>' 'parseJSON' (arr 'V.!' 0)--- '<*>' 'parseJSON' (arr 'V.!' 1)--- _ -> 'mzero'+-- 'Array' arr ->+-- if (V.length arr == 2)+-- then Foo \<$\> 'parseJSON' (arr `V.unsafeIndex` 0)+-- \<*\> 'parseJSON' (arr `V.unsafeIndex` 1)+-- else fail \"\<error message\>\"+-- other -> fail \"\<error message\>\" -- @ deriveFromJSON :: (String -> String) -- ^ Function to change field names.@@ -382,7 +429,7 @@ (classType `appT` instanceType) [ funD 'parseJSON [ clause []- (normalB $ consFromJSON withField cons)+ (normalB $ consFromJSON name withField cons) [] ] ]@@ -402,36 +449,38 @@ -- -- @ -- parseFoo :: 'Value' -> 'Parser' Foo--- parseFoo = $('mkParseJSON' 'id' ''Foo)+-- parseFoo = $('mkParseJSON' id ''Foo) -- @ -- -- This will splice in the following code: -- -- @--- \\value -> case value of arg -> Foo '<$>' 'parseJSON' arg+-- \\value -> case value of arg -> Foo \<$\> 'parseJSON' arg -- @ mkParseJSON :: (String -> String) -- ^ Function to change field names. -> Name -- ^ Name of the encoded type. -> Q Exp mkParseJSON withField name =- withType name (\_ cons -> consFromJSON withField cons)+ withType name (\_ cons -> consFromJSON name withField cons) -- | Helper function used by both 'deriveFromJSON' and 'mkParseJSON'. Generates -- code to parse the JSON encoding of a number of constructors. All constructors -- must be from the same type.-consFromJSON :: (String -> String)+consFromJSON :: Name+ -- ^ Name of the type to which the constructors belong.+ -> (String -> String) -- ^ Function to change field names. -> [Con] -- ^ Constructors for which to generate JSON parsing code. -> Q Exp-consFromJSON _ [] = error $ "Data.Aeson.TH.consFromJSON: "- ++ "Not a single constructor given!"-consFromJSON withField [con] = do+consFromJSON _ _ [] = error $ "Data.Aeson.TH.consFromJSON: "+ ++ "Not a single constructor given!"+consFromJSON tName withField [con] = do value <- newName "value" lam1E (varP value) $ caseE (varE value)- (parseArgs withField con)-consFromJSON withField cons = do+ (parseArgs tName withField con)+consFromJSON tName withField cons = do value <- newName "value" obj <- newName "obj" conKey <- newName "conKey"@@ -440,12 +489,19 @@ let -- Convert the Data.Map inside the Object to a list and pattern match -- against it. It must contain a single element otherwise the parse will -- fail.- caseLst = caseE ([e|M.toList|] `appE` varE obj)+ caseLst = caseE ([e|H.toList|] `appE` varE obj) [ match (listP [tupP [varP conKey, varP conVal]]) (normalB caseKey) []- , errorMatch+ , do other <- newName "other"+ match (varP other)+ (normalB $ [|wrongPairCountFail|]+ `appE` (litE $ stringL $ show tName)+ `appE` ([|show . length|] `appE` varE other)+ )+ [] ]+ caseKey = caseE (varE conKey) [match wildP (guardedB guards) []] guards = [ do g <- normalG $ infixApp (varE conKey)@@ -454,44 +510,59 @@ `appE` conNameExp con ) e <- caseE (varE conVal)- (parseArgs withField con)+ (parseArgs tName withField con) return (g, e) | con <- cons ] ++- [liftM2 (,) (normalG [e|otherwise|]) [e|mzero|]]+ [ liftM2 (,)+ (normalG [e|otherwise|])+ ( [|conNotFoundFail|]+ `appE` (litE $ stringL $ show tName)+ `appE` listE (map (litE . stringL . nameBase . getConName) cons)+ `appE` ([|T.unpack|] `appE` varE conKey)+ )+ ] lam1E (varP value) $ caseE (varE value) [ match (conP 'Object [varP obj]) (normalB caseLst) []- , errorMatch+ , do other <- newName "other"+ match (varP other)+ ( normalB+ $ [|noObjectFail|]+ `appE` (litE $ stringL $ show tName)+ `appE` ([|valueConName|] `appE` varE other)+ )+ [] ]- where- -- Makes a string literal expression from a constructor's name.- conNameExp :: Con -> Q Exp- conNameExp = litE . stringL . nameBase . getConName --- | Generates code to parse the JSON encoding of a single--- constructor.-parseArgs :: (String -> String) -- ^ Function to change field names.+-- | Generates code to parse the JSON encoding of a single constructor.+parseArgs :: Name -- ^ Name of the type to which the constructor belongs.+ -> (String -> String) -- ^ Function to change field names. -> Con -- ^ Constructor for which to generate JSON parsing code. -> [Q Match] -- Nullary constructors.-parseArgs _ (NormalC conName []) =+parseArgs tName _ (NormalC conName []) = [ do arr <- newName "arr"- g <- normalG $ [|V.null|] `appE` varE arr- e <- [e|pure|] `appE` conE conName- -- TODO: Use applicative style: guardedB [(,) <$> g' <*> e']- -- But first need to have "instance Applicative Q". match (conP 'Array [varP arr])- (guardedB [return (g, e)])+ ( normalB $ condE ([|V.null|] `appE` varE arr)+ ([e|pure|] `appE` conE conName)+ ( parseTypeMismatch tName conName+ (litE $ stringL "an empty Array")+ ( infixApp (litE $ stringL $ "Array of length ")+ [|(++)|]+ ([|show . V.length|] `appE` varE arr)+ )+ )+ ) []- , errorMatch+ , matchFailed tName conName "Array" ] -- Unary constructors.-parseArgs _ (NormalC conName [_]) =+parseArgs _ _ (NormalC conName [_]) = [ do arg <- newName "arg" match (varP arg) ( normalB $ infixApp (conE conName)@@ -500,71 +571,144 @@ ) [] ]- -- Polyadic constructors.-parseArgs _ (NormalC conName ts) = parseProduct conName $ genericLength ts+parseArgs tName _ (NormalC conName ts) = parseProduct tName conName $ genericLength ts -- Records.-parseArgs withField (RecC conName ts) =- [ do obj <- newName "obj"- -- List of: "obj .: "<FIELD>""- let x:xs = [ infixApp (varE obj)- [|(.:)|]- ( [e|T.pack|]- `appE`- fieldNameExp withField field- )+parseArgs tName withField (RecC conName ts) =+ [ do obj <- newName "recObj"+ let x:xs = [ [|lookupField|]+ `appE` (litE $ stringL $ show tName)+ `appE` (litE $ stringL $ nameBase conName)+ `appE` (varE obj)+ `appE` ( [e|T.pack|]+ `appE`+ fieldNameExp withField field+ ) | (field, _, _) <- ts ] match (conP 'Object [varP obj])- ( normalB $ foldl' (\a b -> infixApp a [|(<*>)|] b)- (infixApp (conE conName) [|(<$>)|] x)- xs+ ( normalB $ condE ( infixApp ([|H.size|] `appE` varE obj)+ [|(==)|]+ (litE $ integerL $ genericLength ts)+ )+ ( foldl' (\a b -> infixApp a [|(<*>)|] b)+ (infixApp (conE conName) [|(<$>)|] x)+ xs+ )+ ( parseTypeMismatch tName conName+ ( litE $ stringL $ "Object with "+ ++ show (length ts)+ ++ " name/value pairs"+ )+ ( infixApp ([|show . H.size|] `appE` varE obj)+ [|(++)|]+ (litE $ stringL $ " name/value pairs")+ )+ ) ) []- , errorMatch+ , matchFailed tName conName "Object" ] -- Infix constructors. Apart from syntax these are the same as -- polyadic constructors.-parseArgs _ (InfixC _ conName _) = parseProduct conName 2+parseArgs tName _ (InfixC _ conName _) = parseProduct tName conName 2 -- Existentially quantified constructors. We ignore the quantifiers -- and proceed with the contained constructor.-parseArgs withField (ForallC _ _ con) = parseArgs withField con+parseArgs tName withField (ForallC _ _ con) = parseArgs tName withField con -- | Generates code to parse the JSON encoding of an n-ary -- constructor.-parseProduct :: Name -- ^ 'Con'structor name.+parseProduct :: Name -- ^ Name of the type to which the constructor belongs.+ -> Name -- ^ 'Con'structor name. -> Integer -- ^ 'Con'structor arity. -> [Q Match]-parseProduct conName numArgs =+parseProduct tName conName numArgs = [ do arr <- newName "arr"- g <- normalG $ infixApp ([|V.length|] `appE` varE arr)- [|(==)|]- (litE $ integerL numArgs)- -- List of: "parseJSON (arr V.! <IX>)"+ -- List of: "parseJSON (arr `V.unsafeIndex` <IX>)" let x:xs = [ [|parseJSON|] `appE` infixApp (varE arr)- [|(V.!)|]+ [|V.unsafeIndex|] (litE $ integerL ix) | ix <- [0 .. numArgs - 1] ]- e <- foldl' (\a b -> infixApp a [|(<*>)|] b)- (infixApp (conE conName) [|(<$>)|] x)- xs match (conP 'Array [varP arr])- (guardedB [return (g, e)])+ (normalB $ condE ( infixApp ([|V.length|] `appE` varE arr)+ [|(==)|]+ (litE $ integerL numArgs)+ )+ ( foldl' (\a b -> infixApp a [|(<*>)|] b)+ (infixApp (conE conName) [|(<$>)|] x)+ xs+ )+ ( parseTypeMismatch tName conName+ (litE $ stringL $ "Array of length " ++ show numArgs)+ ( infixApp (litE $ stringL $ "Array of length ")+ [|(++)|]+ ([|show . V.length|] `appE` varE arr)+ )+ )+ ) []- , errorMatch+ , matchFailed tName conName "Array" ] --- |--- @--- _ -> 'mzero'--- @-errorMatch :: Q Match-errorMatch = match wildP (normalB [|mzero|]) [] +--------------------------------------------------------------------------------+-- Parsing errors+-------------------------------------------------------------------------------- +matchFailed :: Name -> Name -> String -> MatchQ+matchFailed tName conName expected = do+ other <- newName "other"+ match (varP other)+ ( normalB $ parseTypeMismatch tName conName+ (litE $ stringL expected)+ ([|valueConName|] `appE` varE other)+ )+ []++parseTypeMismatch :: Name -> Name -> ExpQ -> ExpQ -> ExpQ+parseTypeMismatch tName conName expected actual =+ foldl appE+ [|parseTypeMismatch'|]+ [ litE $ stringL $ nameBase conName+ , litE $ stringL $ show tName+ , expected+ , actual+ ]++lookupField :: (FromJSON a) => String -> String -> Object -> T.Text -> Parser a+lookupField tName rec obj key =+ case H.lookup key obj of+ Nothing -> unknownFieldFail tName rec (T.unpack key)+ Just v -> parseJSON v++unknownFieldFail :: String -> String -> String -> Parser fail+unknownFieldFail tName rec key =+ fail $ printf "When parsing the record %s of type %s the key %s was not present."+ rec tName key++noObjectFail :: String -> String -> Parser fail+noObjectFail t o =+ fail $ printf "When parsing %s expected Object but got %s." t o++wrongPairCountFail :: String -> String -> Parser fail+wrongPairCountFail t n =+ fail $ printf "When parsing %s expected an Object with a single name/value pair but got %s pairs."+ t n++conNotFoundFail :: String -> [String] -> String -> Parser fail+conNotFoundFail t cs o =+ fail $ printf "When parsing %s expected an Object with a name/value pair where the name is one of [%s], but got %s."+ t (intercalate ", " cs) o++parseTypeMismatch' :: String -> String -> String -> String -> Parser fail+parseTypeMismatch' tName conName expected actual =+ fail $ printf "When parsing the constructor %s of type %s expected %s but got %s."+ conName tName expected actual++ -------------------------------------------------------------------------------- -- Utility functions --------------------------------------------------------------------------------@@ -604,8 +748,21 @@ tvbName (PlainTV name ) = name tvbName (KindedTV name _) = name +-- | Makes a string literal expression from a constructor's name.+conNameExp :: Con -> Q Exp+conNameExp = litE . stringL . nameBase . getConName+ -- | Creates a string literal expression from a record field name. fieldNameExp :: (String -> String) -- ^ Function to change the field name. -> Name -> Q Exp fieldNameExp f = litE . stringL . f . nameBase++-- | The name of the outermost 'Value' constructor.+valueConName :: Value -> String+valueConName (Object _) = "Object"+valueConName (Array _) = "Array"+valueConName (String _) = "String"+valueConName (Number _) = "Number"+valueConName (Bool _) = "Boolean"+valueConName Null = "Null"
Data/Aeson/Types.hs view
@@ -1,1133 +1,48 @@-{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, GeneralizedNewtypeDeriving,- IncoherentInstances, OverlappingInstances, OverloadedStrings, Rank2Types,- ViewPatterns, FlexibleContexts, UndecidableInstances,- ScopedTypeVariables, PatternGuards #-}--{-# LANGUAGE CPP #-}-#ifdef DEFAULT_SIGNATURES-{-# LANGUAGE DefaultSignatures #-}-#endif---- |--- Module: Data.Aeson.Types--- Copyright: (c) 2011 MailRank, Inc.--- License: Apache--- Maintainer: Bryan O'Sullivan <bos@mailrank.com>--- Stability: experimental--- Portability: portable------ Types for working with JSON data.--module Data.Aeson.Types- (- -- * Core JSON types- Value(..)- , Array- , emptyArray- , Pair- , Object- , emptyObject- -- * Convenience types and functions- , DotNetTime(..)- , typeMismatch- -- * Type conversion- , Parser- , Result(..)- , FromJSON(..)- , fromJSON- , parse- , parseEither- , parseMaybe- , ToJSON(..)- -- * Constructors and accessors- , (.=)- , (.:)- , (.:?)- , object- -- * Generic toJSON and fromJSON- , genericToJSON- , genericFromJSON- ) where--import Control.Applicative-import Control.Arrow (first)-import Control.Monad.State.Strict-import Control.DeepSeq (NFData(..))-import Data.Aeson.Functions-import Data.Attoparsec.Char8 (Number(..))-import Data.Generics-import Data.Hashable (Hashable(..))-import Data.Int (Int8, Int16, Int32, Int64)-import Data.IntSet (IntSet)-import Data.List (foldl')-import Data.Map (Map)-import Data.Maybe (fromJust)-import Data.Monoid (Dual(..), First(..), Last(..))-import Data.Monoid (Monoid(..))-import Data.Ratio (Ratio)-import Data.String (IsString(..))-import Data.Text (Text, pack, unpack)-import Data.Text.Encoding (encodeUtf8)-import Data.Time.Clock (UTCTime)-import Data.Time.Format (FormatTime, formatTime, parseTime)-import Data.Vector (Vector)-import Data.Word (Word, Word8, Word16, Word32, Word64)-import Foreign.Storable (Storable)-import System.Locale (defaultTimeLocale)-import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as LB-import qualified Data.HashMap.Strict as H-import qualified Data.HashSet as HashSet-import qualified Data.IntSet as IntSet-import qualified Data.Map as M-import qualified Data.Set as Set-import qualified Data.Text as T-import qualified Data.Text.Lazy as LT-import qualified Data.Traversable as T-import qualified Data.Vector as V-import qualified Data.Vector.Storable as VS-import qualified Data.Vector.Primitive as VP-import qualified Data.Vector.Unboxed as VU-import qualified Data.Vector.Generic as VG----- | The result of running a 'Parser'.-data Result a = Error String- | Success a- deriving (Eq, Show, Typeable)--instance (NFData a) => NFData (Result a) where- rnf (Success a) = rnf a- rnf (Error err) = rnf err--instance Functor Result where- fmap f (Success a) = Success (f a)- fmap _ (Error err) = Error err- {-# INLINE fmap #-}--instance Monad Result where- return = Success- {-# INLINE return #-}- Success a >>= k = k a- Error err >>= _ = Error err- {-# INLINE (>>=) #-}--instance Applicative Result where- pure = return- {-# INLINE pure #-}- (<*>) = ap- {-# INLINE (<*>) #-}--instance MonadPlus Result where- mzero = fail "mzero"- {-# INLINE mzero #-}- mplus a@(Success _) _ = a- mplus _ b = b- {-# INLINE mplus #-}--instance Alternative Result where- empty = mzero- {-# INLINE empty #-}- (<|>) = mplus- {-# INLINE (<|>) #-}--instance Monoid (Result a) where- mempty = fail "mempty"- {-# INLINE mempty #-}- mappend = mplus- {-# INLINE mappend #-}---- | Failure continuation.-type Failure f r = String -> f r--- | Success continuation.-type Success a f r = a -> f r---- | A continuation-based parser type.-newtype Parser a = Parser {- runParser :: forall f r.- Failure f r- -> Success a f r- -> f r- }--instance Monad Parser where- m >>= g = Parser $ \kf ks -> let ks' a = runParser (g a) kf ks- in runParser m kf ks'- {-# INLINE (>>=) #-}- return a = Parser $ \_kf ks -> ks a- {-# INLINE return #-}- fail msg = Parser $ \kf _ks -> kf msg- {-# INLINE fail #-}--instance Functor Parser where- fmap f m = Parser $ \kf ks -> let ks' a = ks (f a)- in runParser m kf ks'- {-# INLINE fmap #-}--instance Applicative Parser where- pure = return- {-# INLINE pure #-}- (<*>) = apP- {-# INLINE (<*>) #-}- -instance Alternative Parser where- empty = fail "empty"- {-# INLINE empty #-}- (<|>) = mplus- {-# INLINE (<|>) #-}--instance MonadPlus Parser where- mzero = fail "mzero"- {-# INLINE mzero #-}- mplus a b = Parser $ \kf ks -> let kf' _ = runParser b kf ks- in runParser a kf' ks- {-# INLINE mplus #-}--instance Monoid (Parser a) where- mempty = fail "mempty"- {-# INLINE mempty #-}- mappend = mplus- {-# INLINE mappend #-}--apP :: Parser (a -> b) -> Parser a -> Parser b-apP d e = do- b <- d- a <- e- return (b a)-{-# INLINE apP #-}---- | A JSON \"object\" (key\/value map).-type Object = Map Text Value---- | A JSON \"array\" (sequence).-type Array = Vector Value---- | A JSON value represented as a Haskell value.-data Value = Object Object- | Array Array- | String Text- | Number Number- | Bool !Bool- | Null- deriving (Eq, Show, Typeable, Data)--instance NFData Value where- rnf (Object o) = obj_rnf o- rnf (Array a) = V.foldl' (\x y -> rnf y `seq` x) () a- rnf (String s) = rnf s- rnf (Number n) = case n of I i -> rnf i; D d -> rnf d- rnf (Bool b) = rnf b- rnf Null = ()--obj_rnf :: (NFData k, NFData v) => Map k v -> ()-#if MIN_VERSION_containers(0,4,2)-obj_rnf = rnf-#else-obj_rnf = rnf . M.toList-#endif--instance IsString Value where- fromString = String . pack- {-# INLINE fromString #-}--instance Hashable Value where- hash (Object o) = foldl' hashWithSalt 0 . M.toList $ o- hash (Array a) = V.foldl' hashWithSalt 1 a- hash (String s) = 2 `hashWithSalt` s- hash (Number n) = 3 `hashWithSalt` case n of I i -> hash i; D d -> hash d- hash (Bool b) = 4 `hashWithSalt` b- hash Null = 5---- | The empty array.-emptyArray :: Value-emptyArray = Array V.empty---- | The empty object.-emptyObject :: Value-emptyObject = Object M.empty---- | A key\/value pair for an 'Object'.-type Pair = (Text, Value)---- | Construct a 'Pair' from a key and a value.-(.=) :: ToJSON a => Text -> a -> Pair-name .= value = (name, toJSON value)-{-# INLINE (.=) #-}---- | Convert a value from JSON, failing if the types do not match.-fromJSON :: (FromJSON a) => Value -> Result a-fromJSON = parse parseJSON-{-# INLINE fromJSON #-}---- | Run a 'Parser'.-parse :: (a -> Parser b) -> a -> Result b-parse m v = runParser (m v) Error Success-{-# INLINE parse #-}---- | Run a 'Parser' with a 'Maybe' result type.-parseMaybe :: (a -> Parser b) -> a -> Maybe b-parseMaybe m v = runParser (m v) (const Nothing) Just-{-# INLINE parseMaybe #-}---- | Run a 'Parser' with an 'Either' result type.-parseEither :: (a -> Parser b) -> a -> Either String b-parseEither m v = runParser (m v) Left Right-{-# INLINE parseEither #-}---- | Retrieve the value associated with the given key of an 'Object'.--- The result is 'empty' if the key is not present or the value cannot--- be converted to the desired type.------ This accessor is appropriate if the key and value /must/ be present--- in an object for it to be valid. If the key and value are--- optional, use '(.:?)' instead.-(.:) :: (FromJSON a) => Object -> Text -> Parser a-obj .: key = case M.lookup key obj of- Nothing -> fail $ "key " ++ show key ++ " not present"- Just v -> parseJSON v-{-# INLINE (.:) #-}---- | Retrieve the value associated with the given key of an 'Object'.--- The result is 'Nothing' if the key is not present, or 'empty' if--- the value cannot be converted to the desired type.------ This accessor is most useful if the key and value can be absent--- from an object without affecting its validity. If the key and--- value are mandatory, use '(.:)' instead.-(.:?) :: (FromJSON a) => Object -> Text -> Parser (Maybe a)-obj .:? key = case M.lookup key obj of- Nothing -> pure Nothing- Just v -> parseJSON v-{-# INLINE (.:?) #-}---- | Create a 'Value' from a list of name\/value 'Pair's. If duplicate--- keys arise, earlier keys and their associated values win.-object :: [Pair] -> Value-object = Object . M.fromList-{-# INLINE object #-}---- | A type that can be converted to JSON.------ An example type and instance:------ @data Coord { x :: Double, y :: Double }------ instance ToJSON Coord where--- toJSON (Coord x y) = 'object' [\"x\" '.=' x, \"y\" '.=' y]--- @------ This example assumes the OverloadedStrings language option is enabled.-class ToJSON a where- toJSON :: a -> Value--#ifdef DEFAULT_SIGNATURES- default toJSON :: Data a => a -> Value- toJSON = genericToJSON-#endif---- | A type that can be converted from JSON, with the possibility of--- failure.------ When writing an instance, use 'mzero' or 'fail' to make a--- conversion fail, e.g. if an 'Object' is missing a required key, or--- the value is of the wrong type.------ An example type and instance:------ @data Coord { x :: Double, y :: Double }--- --- instance FromJSON Coord where--- parseJSON ('Object' v) = Coord '<$>'--- v '.:' \"x\" '<*>'--- v '.:' \"y\"------ \-- A non-'Object' value is of the wrong type, so use 'mzero' to fail.--- parseJSON _ = 'mzero'--- @------ This example assumes the OverloadedStrings language option is enabled.-class FromJSON a where- parseJSON :: Value -> Parser a--#ifdef DEFAULT_SIGNATURES- default parseJSON :: Data a => Value -> Parser a- parseJSON = genericParseJSON-#endif--instance (ToJSON a) => ToJSON (Maybe a) where- toJSON (Just a) = toJSON a- toJSON Nothing = Null- {-# INLINE toJSON #-}- -instance (FromJSON a) => FromJSON (Maybe a) where- parseJSON Null = pure Nothing- parseJSON a = Just <$> parseJSON a- {-# INLINE parseJSON #-}--instance (ToJSON a, ToJSON b) => ToJSON (Either a b) where- toJSON (Left a) = object [left .= a]- toJSON (Right b) = object [right .= b]- {-# INLINE toJSON #-}- -instance (FromJSON a, FromJSON b) => FromJSON (Either a b) where- parseJSON (Object (M.toList -> [(key, value)]))- | key == left = Left <$> parseJSON value- | key == right = Right <$> parseJSON value- parseJSON _ = mzero- {-# INLINE parseJSON #-}--left, right :: Text-left = "Left"-right = "Right"--instance ToJSON Bool where- toJSON = Bool- {-# INLINE toJSON #-}--instance FromJSON Bool where- parseJSON (Bool b) = pure b- parseJSON v = typeMismatch "Bool" v- {-# INLINE parseJSON #-}--instance ToJSON () where- toJSON _ = emptyArray- {-# INLINE toJSON #-}--instance FromJSON () where- parseJSON (Array v) | V.null v = pure ()- parseJSON v = typeMismatch "()" v- {-# INLINE parseJSON #-}--instance ToJSON [Char] where- toJSON = String . T.pack- {-# INLINE toJSON #-}--instance FromJSON [Char] where- parseJSON (String t) = pure (T.unpack t)- parseJSON v = typeMismatch "String" v- {-# INLINE parseJSON #-}--instance ToJSON Char where- toJSON = String . T.singleton- {-# INLINE toJSON #-}--instance FromJSON Char where- parseJSON (String t)- | T.compareLength t 1 == EQ = pure (T.head t)- parseJSON v = typeMismatch "Char" v- {-# INLINE parseJSON #-}--instance ToJSON Double where- toJSON = Number . D- {-# INLINE toJSON #-}--instance FromJSON Double where- parseJSON (Number n) = case n of- D d -> pure d- I i -> pure (fromIntegral i)- parseJSON Null = pure (0/0)- parseJSON v = typeMismatch "Double" v- {-# INLINE parseJSON #-}--instance ToJSON Number where- toJSON = Number- {-# INLINE toJSON #-}--instance FromJSON Number where- parseJSON (Number n) = pure n- parseJSON Null = pure (D (0/0))- parseJSON v = typeMismatch "Number" v- {-# INLINE parseJSON #-}--instance ToJSON Float where- toJSON = Number . realToFrac- {-# INLINE toJSON #-}--instance FromJSON Float where- parseJSON (Number n) = pure $ case n of- D d -> realToFrac d- I i -> fromIntegral i- parseJSON Null = pure (0/0)- parseJSON v = typeMismatch "Float" v- {-# INLINE parseJSON #-}--instance ToJSON (Ratio Integer) where- toJSON = Number . fromRational- {-# INLINE toJSON #-}--instance FromJSON (Ratio Integer) where- parseJSON (Number n) = pure $ case n of- D d -> toRational d- I i -> fromIntegral i- parseJSON v = typeMismatch "Ratio Integer" v- {-# INLINE parseJSON #-}--instance ToJSON Int where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Int where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--parseIntegral :: Integral a => Value -> Parser a-parseIntegral (Number n) = pure (floor n)-parseIntegral v = typeMismatch "Integral" v-{-# INLINE parseIntegral #-}--instance ToJSON Integer where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Integer where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--instance ToJSON Int8 where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Int8 where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--instance ToJSON Int16 where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Int16 where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--instance ToJSON Int32 where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Int32 where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--instance ToJSON Int64 where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Int64 where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--instance ToJSON Word where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Word where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--instance ToJSON Word8 where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Word8 where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--instance ToJSON Word16 where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Word16 where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--instance ToJSON Word32 where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Word32 where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--instance ToJSON Word64 where- toJSON = Number . fromIntegral- {-# INLINE toJSON #-}--instance FromJSON Word64 where- parseJSON = parseIntegral- {-# INLINE parseJSON #-}--instance ToJSON Text where- toJSON = String- {-# INLINE toJSON #-}--instance FromJSON Text where- parseJSON (String t) = pure t- parseJSON v = typeMismatch "Text" v- {-# INLINE parseJSON #-}--instance ToJSON LT.Text where- toJSON = String . LT.toStrict- {-# INLINE toJSON #-}--instance FromJSON LT.Text where- parseJSON (String t) = pure (LT.fromStrict t)- parseJSON v = typeMismatch "Lazy Text" v- {-# INLINE parseJSON #-}--instance ToJSON B.ByteString where- toJSON = String . decode- {-# INLINE toJSON #-}--instance FromJSON B.ByteString where- parseJSON (String t) = pure . encodeUtf8 $ t- parseJSON v = typeMismatch "ByteString" v- {-# INLINE parseJSON #-}--instance ToJSON LB.ByteString where- toJSON = toJSON . strict- {-# INLINE toJSON #-}--instance FromJSON LB.ByteString where- parseJSON (String t) = pure . lazy $ t- parseJSON v = typeMismatch "Lazy ByteString" v- {-# INLINE parseJSON #-}--instance (ToJSON a) => ToJSON [a] where- toJSON = Array . V.fromList . map toJSON- {-# INLINE toJSON #-}- -instance (FromJSON a) => FromJSON [a] where- parseJSON (Array a) = mapM parseJSON (V.toList a)- parseJSON v = typeMismatch "[a]" v- {-# INLINE parseJSON #-}--instance (ToJSON a) => ToJSON (Vector a) where- toJSON = Array . V.map toJSON- {-# INLINE toJSON #-}- -instance (FromJSON a) => FromJSON (Vector a) where- parseJSON (Array a) = V.mapM parseJSON a- parseJSON v = typeMismatch "Vector a" v- {-# INLINE parseJSON #-}--vectorToJSON :: (VG.Vector v a, ToJSON a) => v a -> Value-vectorToJSON = Array . V.map toJSON . V.convert-{-# INLINE vectorToJSON #-}--vectorParseJSON :: (FromJSON a, VG.Vector w a) => String -> Value -> Parser (w a)-vectorParseJSON _ (Array a) = V.convert <$> V.mapM parseJSON a-vectorParseJSON s v = typeMismatch s v-{-# INLINE vectorParseJSON #-}--instance (Storable a, ToJSON a) => ToJSON (VS.Vector a) where- toJSON = vectorToJSON--instance (Storable a, FromJSON a) => FromJSON (VS.Vector a) where- parseJSON = vectorParseJSON "Data.Vector.Storable.Vector a"--instance (VP.Prim a, ToJSON a) => ToJSON (VP.Vector a) where- toJSON = vectorToJSON--instance (VP.Prim a, FromJSON a) => FromJSON (VP.Vector a) where- parseJSON = vectorParseJSON "Data.Vector.Primitive.Vector a"--instance (VG.Vector VU.Vector a, ToJSON a) => ToJSON (VU.Vector a) where- toJSON = vectorToJSON--instance (VG.Vector VU.Vector a, FromJSON a) => FromJSON (VU.Vector a) where- parseJSON = vectorParseJSON "Data.Vector.Unboxed.Vector a"--instance (ToJSON a) => ToJSON (Set.Set a) where- toJSON = toJSON . Set.toList- {-# INLINE toJSON #-}- -instance (Ord a, FromJSON a) => FromJSON (Set.Set a) where- parseJSON = fmap Set.fromList . parseJSON- {-# INLINE parseJSON #-}--instance (ToJSON a) => ToJSON (HashSet.HashSet a) where- toJSON = toJSON . HashSet.toList- {-# INLINE toJSON #-}- -instance (Eq a, Hashable a, FromJSON a) => FromJSON (HashSet.HashSet a) where- parseJSON = fmap HashSet.fromList . parseJSON- {-# INLINE parseJSON #-}--instance ToJSON IntSet.IntSet where- toJSON = toJSON . IntSet.toList- {-# INLINE toJSON #-}- -instance FromJSON IntSet.IntSet where- parseJSON = fmap IntSet.fromList . parseJSON- {-# INLINE parseJSON #-}--instance (ToJSON v) => ToJSON (M.Map Text v) where- toJSON = Object . M.map toJSON- {-# INLINE toJSON #-}--instance (FromJSON v) => FromJSON (M.Map Text v) where- parseJSON (Object o) = M.fromAscList <$> mapM go (M.toAscList o)- where go (k,v) = ((,) k) <$> parseJSON v- parseJSON v = typeMismatch "Map Text a" v--instance (ToJSON v) => ToJSON (M.Map LT.Text v) where- toJSON = Object . transformMap LT.toStrict toJSON--instance (FromJSON v) => FromJSON (M.Map LT.Text v) where- parseJSON = fmap (M.mapKeysMonotonic LT.fromStrict) . parseJSON--instance (ToJSON v) => ToJSON (M.Map String v) where- toJSON = Object . transformMap pack toJSON--instance (FromJSON v) => FromJSON (M.Map String v) where- parseJSON = fmap (M.mapKeysMonotonic unpack) . parseJSON--instance (ToJSON v) => ToJSON (M.Map B.ByteString v) where- toJSON = Object . transformMap decode toJSON--instance (FromJSON v) => FromJSON (M.Map B.ByteString v) where- parseJSON = fmap (M.mapKeysMonotonic encodeUtf8) . parseJSON--instance (ToJSON v) => ToJSON (M.Map LB.ByteString v) where- toJSON = Object . transformMap strict toJSON--instance (FromJSON v) => FromJSON (M.Map LB.ByteString v) where- parseJSON = fmap (M.mapKeysMonotonic lazy) . parseJSON--instance (ToJSON v) => ToJSON (H.HashMap Text v) where- toJSON = Object . hashMap id toJSON- {-# INLINE toJSON #-}--instance (FromJSON v) => FromJSON (H.HashMap Text v) where- parseJSON (Object o) = H.fromList <$> mapM go (M.toList o)- where go (k,v) = ((,) k) <$> parseJSON v- parseJSON v = typeMismatch "HashMap Text a" v--instance (ToJSON v) => ToJSON (H.HashMap LT.Text v) where- toJSON = Object . M.fromList . H.foldrWithKey (\k v -> ((LT.toStrict k,toJSON v) :)) []--instance (FromJSON v) => FromJSON (H.HashMap LT.Text v) where- parseJSON = fmap (mapHash LT.fromStrict) . parseJSON--instance (ToJSON v) => ToJSON (H.HashMap String v) where- toJSON = Object . hashMap pack toJSON--instance (FromJSON v) => FromJSON (H.HashMap String v) where- parseJSON = fmap (mapHash unpack) . parseJSON--instance (ToJSON v) => ToJSON (H.HashMap B.ByteString v) where- toJSON = Object . hashMap decode toJSON--instance (FromJSON v) => FromJSON (H.HashMap B.ByteString v) where- parseJSON = fmap (mapHash encodeUtf8) . parseJSON--instance (ToJSON v) => ToJSON (H.HashMap LB.ByteString v) where- toJSON = Object . hashMap strict toJSON--instance (FromJSON v) => FromJSON (H.HashMap LB.ByteString v) where- parseJSON = fmap (mapHash lazy) . parseJSON--instance ToJSON Value where- toJSON a = a- {-# INLINE toJSON #-}--instance FromJSON Value where- parseJSON a = pure a- {-# INLINE parseJSON #-}---- | A newtype wrapper for 'UTCTime' that uses the same non-standard--- serialization format as Microsoft .NET, whose @System.DateTime@--- type is by default serialized to JSON as in the following example:------ > /Date(1302547608878)/------ The number represents milliseconds since the Unix epoch.-newtype DotNetTime = DotNetTime {- fromDotNetTime :: UTCTime- } deriving (Eq, Ord, Read, Show, Typeable, FormatTime)--instance ToJSON DotNetTime where- toJSON (DotNetTime t) =- String (pack (secs ++ msecs ++ ")/"))- where secs = formatTime defaultTimeLocale "/Date(%s" t- msecs = take 3 $ formatTime defaultTimeLocale "%q" t- {-# INLINE toJSON #-}--instance FromJSON DotNetTime where- parseJSON (String t) =- case parseTime defaultTimeLocale "/Date(%s%Q)/" (unpack t') of- Just d -> pure (DotNetTime d)- _ -> fail "could not parse .NET time"- where (s,m) = T.splitAt (T.length t - 5) t- t' = T.concat [s,".",m]- parseJSON v = typeMismatch "DotNetTime" v- {-# INLINE parseJSON #-}--instance ToJSON UTCTime where- toJSON t = String (pack (take 23 str ++ "Z"))- where str = formatTime defaultTimeLocale "%FT%T%Q" t- {-# INLINE toJSON #-}--instance FromJSON UTCTime where- parseJSON (String t) =- case parseTime defaultTimeLocale "%FT%T%QZ" (unpack t) of- Just d -> pure d- _ -> fail "could not parse ISO-8601 date"- parseJSON v = typeMismatch "UTCTime" v- {-# INLINE parseJSON #-}--instance (ToJSON a, ToJSON b) => ToJSON (a,b) where- toJSON (a,b) = toJSON [toJSON a, toJSON b]- {-# INLINE toJSON #-}--instance (FromJSON a, FromJSON b) => FromJSON (a,b) where- parseJSON (Array ab) =- case V.toList ab of- [a,b] -> (,) <$> parseJSON a <*> parseJSON b- _ -> fail $ "cannot unpack array of length " ++- show (V.length ab) ++ " into a pair"- parseJSON v = typeMismatch "(a,b)" v- {-# INLINE parseJSON #-}--instance (ToJSON a, ToJSON b, ToJSON c) => ToJSON (a,b,c) where- toJSON (a,b,c) = toJSON [toJSON a, toJSON b, toJSON c]- {-# INLINE toJSON #-}--instance (FromJSON a, FromJSON b, FromJSON c) => FromJSON (a,b,c) where- parseJSON (Array abc) =- case V.toList abc of- [a,b,c] -> (,,) <$> parseJSON a <*> parseJSON b <*> parseJSON c- _ -> fail $ "cannot unpack array of length " ++- show (V.length abc) ++ " into a 3-tuple"- parseJSON v = typeMismatch "(a,b,c)" v- {-# INLINE parseJSON #-}--instance ToJSON a => ToJSON (Dual a) where- toJSON = toJSON . getDual- {-# INLINE toJSON #-}--instance FromJSON a => FromJSON (Dual a) where- parseJSON = fmap Dual . parseJSON- {-# INLINE parseJSON #-}--instance ToJSON a => ToJSON (First a) where- toJSON = toJSON . getFirst- {-# INLINE toJSON #-}--instance FromJSON a => FromJSON (First a) where- parseJSON = fmap First . parseJSON- {-# INLINE parseJSON #-}--instance ToJSON a => ToJSON (Last a) where- toJSON = toJSON . getLast- {-# INLINE toJSON #-}--instance FromJSON a => FromJSON (Last a) where- parseJSON = fmap Last . parseJSON- {-# INLINE parseJSON #-}---- | Fail parsing due to a type mismatch, with a descriptive message.-typeMismatch :: String -- ^ The name of the type you are trying to parse.- -> Value -- ^ The actual value encountered.- -> Parser a-typeMismatch expected actual =- fail $ "when expecting a " ++ expected ++ ", encountered " ++ name ++- " instead"- where- name = case actual of- Object _ -> "Object"- Array _ -> "Array"- String _ -> "String"- Number _ -> "Number"- Bool _ -> "Boolean"- Null -> "Null"-------------------------------------------------------------------------------------- Generic toJSON and fromJSON--type T a = a -> Value--genericToJSON :: (Data a) => a -> Value-genericToJSON = toJSON_generic- `ext1Q` list- `ext1Q` vector- `ext1Q` set- `ext2Q'` mapAny- `ext2Q'` hashMapAny- -- Use the standard encoding for all base types.- `extQ` (toJSON :: T Integer)- `extQ` (toJSON :: T Int)- `extQ` (toJSON :: T Int8)- `extQ` (toJSON :: T Int16)- `extQ` (toJSON :: T Int32)- `extQ` (toJSON :: T Int64)- `extQ` (toJSON :: T Word)- `extQ` (toJSON :: T Word8)- `extQ` (toJSON :: T Word16)- `extQ` (toJSON :: T Word32)- `extQ` (toJSON :: T Word64)- `extQ` (toJSON :: T Double)- `extQ` (toJSON :: T Number)- `extQ` (toJSON :: T Float)- `extQ` (toJSON :: T Rational)- `extQ` (toJSON :: T Char)- `extQ` (toJSON :: T Text)- `extQ` (toJSON :: T LT.Text)- `extQ` (toJSON :: T String)- `extQ` (toJSON :: T B.ByteString)- `extQ` (toJSON :: T LB.ByteString)- `extQ` (toJSON :: T Value)- `extQ` (toJSON :: T DotNetTime)- `extQ` (toJSON :: T UTCTime)- `extQ` (toJSON :: T IntSet)- `extQ` (toJSON :: T Bool)- `extQ` (toJSON :: T ())- --`extQ` (T.toJSON :: T Ordering)- where- list xs = Array . V.fromList . map genericToJSON $ xs- vector v = Array . V.map genericToJSON $ v- set s = Array . V.fromList . map genericToJSON . Set.toList $ s-- mapAny m- | tyrep == typeOf T.empty = remap id- | tyrep == typeOf LT.empty = remap LT.toStrict- | tyrep == typeOf string = remap pack- | tyrep == typeOf B.empty = remap decode- | tyrep == typeOf LB.empty = remap strict- | otherwise = modError "genericToJSON" $- "cannot convert map keyed by type " ++ show tyrep- where tyrep = typeOf . head . M.keys $ m- remap f = Object . transformMap (f . fromJust . cast) genericToJSON $ m-- hashMapAny m- | tyrep == typeOf T.empty = remap id- | tyrep == typeOf LT.empty = remap LT.toStrict- | tyrep == typeOf string = remap pack- | tyrep == typeOf B.empty = remap decode- | tyrep == typeOf LB.empty = remap strict- | otherwise = modError "genericToJSON" $- "cannot convert map keyed by type " ++ show tyrep- where tyrep = typeOf . head . H.keys $ m- remap f = Object . hashMap (f . fromJust . cast) genericToJSON $ m---toJSON_generic :: (Data a) => a -> Value-toJSON_generic = generic- where- -- Generic encoding of an algebraic data type.- generic a =- case dataTypeRep (dataTypeOf a) of- -- No constructor, so it must be an error value. Code- -- it anyway as Null.- AlgRep [] -> Null- -- Elide a single constructor and just code the arguments.- AlgRep [c] -> encodeArgs c (gmapQ genericToJSON a)- -- For multiple constructors, make an object with a- -- field name that is the constructor (except lower- -- case) and the data is the arguments encoded.- AlgRep _ -> encodeConstr (toConstr a) (gmapQ genericToJSON a)- rep -> err (dataTypeOf a) rep- where- err dt r = modError "genericToJSON" $ "not AlgRep " ++- show r ++ "(" ++ show dt ++ ")"- -- Encode nullary constructor as a string.- -- Encode non-nullary constructors as an object with the constructor- -- name as the single field and the arguments as the value.- -- Use an array if the are no field names, but elide singleton arrays,- -- and use an object if there are field names.- encodeConstr c [] = String . constrString $ c- encodeConstr c as = object [(constrString c, encodeArgs c as)]-- constrString = pack . showConstr-- encodeArgs c = encodeArgs' (constrFields c)- encodeArgs' [] [j] = j- encodeArgs' [] js = Array . V.fromList $ js- encodeArgs' ns js = object $ zip (map mungeField ns) js-- -- Skip leading '_' in field name so we can use keywords- -- etc. as field names.- mungeField ('_':cs) = pack cs- mungeField cs = pack cs--genericFromJSON :: (Data a) => Value -> Result a-genericFromJSON = parse genericParseJSON--type F a = Parser a--genericParseJSON :: (Data a) => Value -> Parser a-genericParseJSON j = parseJSON_generic j- `ext1R` list- `ext1R` vector- `ext2R'` mapAny- `ext2R'` hashMapAny- -- Use the standard encoding for all base types.- `extR` (value :: F Integer)- `extR` (value :: F Int)- `extR` (value :: F Int8)- `extR` (value :: F Int16)- `extR` (value :: F Int32)- `extR` (value :: F Int64)- `extR` (value :: F Word)- `extR` (value :: F Word8)- `extR` (value :: F Word16)- `extR` (value :: F Word32)- `extR` (value :: F Word64)- `extR` (value :: F Double)- `extR` (value :: F Number)- `extR` (value :: F Float)- `extR` (value :: F Rational)- `extR` (value :: F Char)- `extR` (value :: F Text)- `extR` (value :: F LT.Text)- `extR` (value :: F String)- `extR` (value :: F B.ByteString)- `extR` (value :: F LB.ByteString)- `extR` (value :: F Value)- `extR` (value :: F DotNetTime)- `extR` (value :: F UTCTime)- `extR` (value :: F IntSet)- `extR` (value :: F Bool)- `extR` (value :: F ())- where- value :: (FromJSON a) => Parser a- value = parseJSON j- list :: (Data a) => Parser [a]- list = V.toList <$> genericParseJSON j- vector :: (Data a) => Parser (V.Vector a)- vector = case j of- Array js -> V.mapM genericParseJSON js- _ -> myFail- mapAny :: forall e f. (Data e, Data f) => Parser (Map f e)- mapAny- | tyrep `elem` stringyTypes = res- | otherwise = myFail- where res = case j of- Object js -> M.mapKeysMonotonic trans <$> T.mapM genericParseJSON js- _ -> myFail- trans- | tyrep == typeOf T.empty = remap id- | tyrep == typeOf LT.empty = remap LT.fromStrict- | tyrep == typeOf string = remap T.unpack- | tyrep == typeOf B.empty = remap encodeUtf8- | tyrep == typeOf LB.empty = remap lazy- | otherwise = modError "genericParseJSON"- "mapAny -- should never happen"- tyrep = typeOf (undefined :: f)- remap f = fromJust . cast . f- hashMapAny :: forall e f. (Data e, Data f) => Parser (H.HashMap f e)- hashMapAny- | tyrep == typeOf string = process T.unpack- | tyrep == typeOf LT.empty = process LT.fromStrict- | tyrep == typeOf T.empty = process id- | otherwise = myFail- where- process f = maybe myFail return . cast =<< parseWith f- parseWith :: (Eq c, Hashable c) => (Text -> c) -> Parser (H.HashMap c e)- parseWith f = case j of- Object js -> H.fromList . map (first f) . M.toList <$>- T.mapM genericParseJSON js- _ -> myFail- tyrep = typeOf (undefined :: f)- myFail = modFail "genericParseJSON" $ "bad data: " ++ show j- stringyTypes = [typeOf LT.empty, typeOf T.empty, typeOf B.empty,- typeOf LB.empty, typeOf string]--parseJSON_generic :: (Data a) => Value -> Parser a-parseJSON_generic j = generic- where- typ = dataTypeOf $ resType generic- generic = case dataTypeRep typ of- AlgRep [] -> case j of- Null -> return (modError "genericParseJSON" "empty type")- _ -> modFail "genericParseJSON" "no-constr bad data"- AlgRep [_] -> decodeArgs (indexConstr typ 1) j- AlgRep _ -> do (c, j') <- getConstr typ j; decodeArgs c j'- rep -> modFail "genericParseJSON" $- show rep ++ "(" ++ show typ ++ ")"- getConstr t (Object o) | [(s, j')] <- fromJSObject o = do- c <- readConstr' t s- return (c, j')- getConstr t (String js) = do c <- readConstr' t (unpack js)- return (c, Null) -- handle nullary ctor- getConstr _ _ = modFail "genericParseJSON" "bad constructor encoding"- readConstr' t s =- maybe (modFail "genericParseJSON" $ "unknown constructor: " ++ s ++ " " ++- show t)- return $ readConstr t s-- decodeArgs c0 = go (numConstrArgs (resType generic) c0) c0- (constrFields c0)- where- go 0 c _ Null = construct c [] -- nullary constructor- go 1 c [] jd = construct c [jd] -- unary constructor- go n c [] (Array js)- | n > 1 = construct c (V.toList js) -- no field names- -- FIXME? We could allow reading an array into a constructor- -- with field names.- go _ c fs@(_:_) (Object o) = selectFields o fs >>=- construct c -- field names- go _ c _ jd = modFail "genericParseJSON" $- "bad decodeArgs data " ++ show (c, jd)-- fromJSObject = map (first unpack) . M.toList-- -- Build the value by stepping through the list of subparts.- construct c = evalStateT $ fromConstrM f c- where f :: (Data a) => StateT [Value] Parser a- f = do js <- get- case js of- [] -> lift $ modFail "construct" "empty list"- (j':js') -> do put js'; lift $ genericParseJSON j'-- -- Select the named fields from a JSON object.- selectFields fjs = mapM sel- where sel f = maybe (modFail "genericParseJSON" $ "field does not exist " ++- f) return $ M.lookup (pack f) fjs-- -- Count how many arguments a constructor has. The value x is- -- used to determine what type the constructor returns.- numConstrArgs :: (Data a) => a -> Constr -> Int- numConstrArgs x c = execState (fromConstrM f c `asTypeOf` return x) 0- where f = do modify (+1); return undefined-- resType :: MonadPlus m => m a -> a- resType _ = modError "genericParseJSON" "resType"--modFail :: (Monad m) => String -> String -> m a-modFail func err = fail $ "Data.Aeson.Types." ++ func ++ ": " ++ err--modError :: String -> String -> a-modError func err = error $ "Data.Aeson.Types." ++ func ++ ": " ++ err--string :: String-string = ""---- Type extension for binary type constructors.---- | Flexible type extension-ext2' :: (Data a, Typeable2 t)- => c a- -> (forall d1 d2. (Data d1, Data d2) => c (t d1 d2))- -> c a-ext2' def ext = maybe def id (dataCast2 ext)---- | Type extension of queries for type constructors-ext2Q' :: (Data d, Typeable2 t)- => (d -> q)- -> (forall d1 d2. (Data d1, Data d2) => t d1 d2 -> q)- -> d -> q-ext2Q' def ext = unQ ((Q def) `ext2'` (Q ext))---- | Type extension of readers for type constructors-ext2R' :: (Monad m, Data d, Typeable2 t)- => m d- -> (forall d1 d2. (Data d1, Data d2) => m (t d1 d2))- -> m d-ext2R' def ext = unR ((R def) `ext2'` (R ext))---- | The type constructor for queries-newtype Q q x = Q { unQ :: x -> q }---- | The type constructor for readers-newtype R m x = R { unR :: m x }+{-# LANGUAGE CPP #-}++-- |+-- Module: Data.Aeson.Types+-- Copyright: (c) 2011 Bryan O'Sullivan+-- (c) 2011 MailRank, Inc.+-- License: Apache+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>+-- Stability: experimental+-- Portability: portable+--+-- Types for working with JSON data.++module Data.Aeson.Types+ (+ -- * Core JSON types+ Value(..)+ , Array+ , emptyArray+ , Pair+ , Object+ , emptyObject+ -- * Convenience types and functions+ , DotNetTime(..)+ , typeMismatch+ -- * Type conversion+ , Parser+ , Result(..)+ , FromJSON(..)+ , fromJSON+ , parse+ , parseEither+ , parseMaybe+ , ToJSON(..)+ -- * Constructors and accessors+ , (.=)+ , (.:)+ , (.:?)+ , (.!=)+ , object+ ) where++import Data.Aeson.Types.Class+import Data.Aeson.Types.Internal++#ifdef GENERICS+import Data.Aeson.Types.Generic ()+#endif
+ Data/Aeson/Types/Class.hs view
@@ -0,0 +1,782 @@+{-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, FlexibleInstances,+ GeneralizedNewtypeDeriving, IncoherentInstances, OverlappingInstances,+ OverloadedStrings, UndecidableInstances, ViewPatterns #-}++#ifdef GENERICS+{-# LANGUAGE DefaultSignatures #-}+#endif++-- |+-- Module: Data.Aeson.Types.Class+-- Copyright: (c) 2011 Bryan O'Sullivan+-- (c) 2011 MailRank, Inc.+-- License: Apache+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>+-- Stability: experimental+-- Portability: portable+--+-- Types for working with JSON data.++module Data.Aeson.Types.Class+ (+ -- * Type classes+ -- ** Core JSON classes+ FromJSON(..)+ , ToJSON(..)+#ifdef GENERICS+ -- ** Generic JSON classes+ , GFromJSON(..)+ , GToJSON(..)+#endif+ -- * Types+ , DotNetTime(..)+ -- * Functions+ , fromJSON+ , (.:)+ , (.:?)+ , (.!=)+ , (.=)+ , typeMismatch+ ) where++import Control.Applicative ((<$>), (<*>), pure)+import Data.Aeson.Functions+import Data.Aeson.Types.Internal+import Data.Attoparsec.Char8 (Number(..))+import Data.Hashable (Hashable(..))+import Data.Int (Int8, Int16, Int32, Int64)+import Data.Maybe (fromMaybe)+import Data.Monoid (Dual(..), First(..), Last(..))+import Data.Ratio (Ratio)+import Data.Text (Text, pack, unpack)+import Data.Text.Encoding (encodeUtf8)+import Data.Time.Clock (UTCTime)+import Data.Time.Format (FormatTime, formatTime, parseTime)+import Data.Traversable (traverse)+import Data.Typeable (Typeable)+import Data.Vector (Vector)+import Data.Word (Word, Word8, Word16, Word32, Word64)+import Foreign.Storable (Storable)+import System.Locale (defaultTimeLocale)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as LB+import qualified Data.HashMap.Strict as H+import qualified Data.HashSet as HashSet+import qualified Data.IntMap as IntMap+import qualified Data.IntSet as IntSet+import qualified Data.Map as M+import qualified Data.Set as Set+import qualified Data.Text as T+import qualified Data.Text.Lazy as LT+import qualified Data.Vector as V+import qualified Data.Vector.Generic as VG+import qualified Data.Vector.Primitive as VP+import qualified Data.Vector.Storable as VS+import qualified Data.Vector.Unboxed as VU+import qualified Data.Vector.Mutable as VM ( unsafeNew, unsafeWrite )++#ifdef GENERICS+import GHC.Generics++class GToJSON f where+ gToJSON :: f a -> Value++class GFromJSON f where+ gParseJSON :: Value -> Parser (f a)+#endif++-- | A type that can be converted to JSON.+--+-- An example type and instance:+--+-- @{-\# LANGUAGE OverloadedStrings #-}+--+-- data Coord { x :: Double, y :: Double }+--+-- instance ToJSON Coord where+-- toJSON (Coord x y) = 'object' [\"x\" '.=' x, \"y\" '.=' y]+-- @+--+-- Note the use of the @OverloadedStrings@ language extension which enables+-- 'Text' values to be written as string literals.+--+-- Instead of manually writing your 'ToJSON' instance, there are three options+-- to do it automatically:+--+-- * 'Data.Aeson.TH' provides template-haskell functions which will derive an+-- instance at compile-time. The generated instance is optimized for your type+-- so will probably be more efficient than the following two options:+--+-- * 'Data.Aeson.Generic' provides a generic @toJSON@ function that accepts any+-- type which is an instance of 'Data'.+--+-- * If your compiler has support for the @DeriveGeneric@ and+-- @DefaultSignatures@ language extensions, @toJSON@ will have a default generic+-- implementation.+--+-- To use the latter option, simply add a @deriving 'Generic'@ clause to your+-- datatype and declare a @ToJSON@ instance for your datatype without giving a+-- definition for @toJSON@.+--+-- For example the previous example can be simplified to just:+--+-- @{-\# LANGUAGE DeriveGeneric \#-}+--+-- import GHC.Generics+--+-- data Coord { x :: Double, y :: Double } deriving Generic+--+-- instance ToJSON Coord+-- @+class ToJSON a where+ toJSON :: a -> Value++#ifdef GENERICS+ default toJSON :: (Generic a, GToJSON (Rep a)) => a -> Value+ toJSON = gToJSON . from+#endif++-- | A type that can be converted from JSON, with the possibility of+-- failure.+--+-- When writing an instance, use 'empty', 'mzero', or 'fail' to make a+-- conversion fail, e.g. if an 'Object' is missing a required key, or+-- the value is of the wrong type.+--+-- An example type and instance:+--+-- @{-\# LANGUAGE OverloadedStrings #-}+--+-- data Coord { x :: Double, y :: Double }+--+-- instance FromJSON Coord where+-- parseJSON ('Object' v) = Coord '<$>'+-- v '.:' \"x\" '<*>'+-- v '.:' \"y\"+--+-- \-- A non-'Object' value is of the wrong type, so use 'mzero' to fail.+-- parseJSON _ = 'mzero'+-- @+--+-- Note the use of the @OverloadedStrings@ language extension which enables+-- 'Text' values to be written as string literals.+--+-- Instead of manually writing your 'FromJSON' instance, there are three options+-- to do it automatically:+--+-- * 'Data.Aeson.TH' provides template-haskell functions which will derive an+-- instance at compile-time. The generated instance is optimized for your type+-- so will probably be more efficient than the following two options:+--+-- * 'Data.Aeson.Generic' provides a generic @fromJSON@ function that parses to+-- any type which is an instance of 'Data'.+--+-- * If your compiler has support for the @DeriveGeneric@ and+-- @DefaultSignatures@ language extensions, @parseJSON@ will have a default+-- generic implementation.+--+-- To use this, simply add a @deriving 'Generic'@ clause to your datatype and+-- declare a @FromJSON@ instance for your datatype without giving a definition+-- for @parseJSON@.+--+-- For example the previous example can be simplified to just:+--+-- @{-\# LANGUAGE DeriveGeneric \#-}+--+-- import GHC.Generics+--+-- data Coord { x :: Double, y :: Double } deriving Generic+--+-- instance FromJSON Coord+-- @+class FromJSON a where+ parseJSON :: Value -> Parser a++#ifdef GENERICS+ default parseJSON :: (Generic a, GFromJSON (Rep a)) => Value -> Parser a+ parseJSON = fmap to . gParseJSON+#endif++instance (ToJSON a) => ToJSON (Maybe a) where+ toJSON (Just a) = toJSON a+ toJSON Nothing = Null+ {-# INLINE toJSON #-}++instance (FromJSON a) => FromJSON (Maybe a) where+ parseJSON Null = pure Nothing+ parseJSON a = Just <$> parseJSON a+ {-# INLINE parseJSON #-}++instance (ToJSON a, ToJSON b) => ToJSON (Either a b) where+ toJSON (Left a) = object [left .= a]+ toJSON (Right b) = object [right .= b]+ {-# INLINE toJSON #-}++instance (FromJSON a, FromJSON b) => FromJSON (Either a b) where+ parseJSON (Object (H.toList -> [(key, value)]))+ | key == left = Left <$> parseJSON value+ | key == right = Right <$> parseJSON value+ parseJSON _ = fail ""+ {-# INLINE parseJSON #-}++left, right :: Text+left = "Left"+right = "Right"++instance ToJSON Bool where+ toJSON = Bool+ {-# INLINE toJSON #-}++instance FromJSON Bool where+ parseJSON (Bool b) = pure b+ parseJSON v = typeMismatch "Bool" v+ {-# INLINE parseJSON #-}++instance ToJSON () where+ toJSON _ = emptyArray+ {-# INLINE toJSON #-}++instance FromJSON () where+ parseJSON (Array v) | V.null v = pure ()+ parseJSON v = typeMismatch "()" v+ {-# INLINE parseJSON #-}++instance ToJSON [Char] where+ toJSON = String . T.pack+ {-# INLINE toJSON #-}++instance FromJSON [Char] where+ parseJSON (String t) = pure (T.unpack t)+ parseJSON v = typeMismatch "String" v+ {-# INLINE parseJSON #-}++instance ToJSON Char where+ toJSON = String . T.singleton+ {-# INLINE toJSON #-}++instance FromJSON Char where+ parseJSON (String t)+ | T.compareLength t 1 == EQ = pure (T.head t)+ parseJSON v = typeMismatch "Char" v+ {-# INLINE parseJSON #-}++instance ToJSON Double where+ toJSON = Number . D+ {-# INLINE toJSON #-}++instance FromJSON Double where+ parseJSON (Number n) = case n of+ D d -> pure d+ I i -> pure (fromIntegral i)+ parseJSON Null = pure (0/0)+ parseJSON v = typeMismatch "Double" v+ {-# INLINE parseJSON #-}++instance ToJSON Number where+ toJSON = Number+ {-# INLINE toJSON #-}++instance FromJSON Number where+ parseJSON (Number n) = pure n+ parseJSON Null = pure (D (0/0))+ parseJSON v = typeMismatch "Number" v+ {-# INLINE parseJSON #-}++instance ToJSON Float where+ toJSON = Number . realToFrac+ {-# INLINE toJSON #-}++instance FromJSON Float where+ parseJSON (Number n) = pure $ case n of+ D d -> realToFrac d+ I i -> fromIntegral i+ parseJSON Null = pure (0/0)+ parseJSON v = typeMismatch "Float" v+ {-# INLINE parseJSON #-}++instance ToJSON (Ratio Integer) where+ toJSON = Number . fromRational+ {-# INLINE toJSON #-}++instance FromJSON (Ratio Integer) where+ parseJSON (Number n) = pure $ case n of+ D d -> toRational d+ I i -> fromIntegral i+ parseJSON v = typeMismatch "Ratio Integer" v+ {-# INLINE parseJSON #-}++instance ToJSON Int where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Int where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++parseIntegral :: Integral a => Value -> Parser a+parseIntegral (Number n) = pure (floor n)+parseIntegral v = typeMismatch "Integral" v+{-# INLINE parseIntegral #-}++instance ToJSON Integer where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Integer where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++instance ToJSON Int8 where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Int8 where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++instance ToJSON Int16 where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Int16 where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++instance ToJSON Int32 where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Int32 where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++instance ToJSON Int64 where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Int64 where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++instance ToJSON Word where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Word where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++instance ToJSON Word8 where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Word8 where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++instance ToJSON Word16 where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Word16 where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++instance ToJSON Word32 where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Word32 where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++instance ToJSON Word64 where+ toJSON = Number . fromIntegral+ {-# INLINE toJSON #-}++instance FromJSON Word64 where+ parseJSON = parseIntegral+ {-# INLINE parseJSON #-}++instance ToJSON Text where+ toJSON = String+ {-# INLINE toJSON #-}++instance FromJSON Text where+ parseJSON (String t) = pure t+ parseJSON v = typeMismatch "Text" v+ {-# INLINE parseJSON #-}++instance ToJSON LT.Text where+ toJSON = String . LT.toStrict+ {-# INLINE toJSON #-}++instance FromJSON LT.Text where+ parseJSON (String t) = pure (LT.fromStrict t)+ parseJSON v = typeMismatch "Lazy Text" v+ {-# INLINE parseJSON #-}++instance ToJSON B.ByteString where+ toJSON = String . decode+ {-# INLINE toJSON #-}++instance FromJSON B.ByteString where+ parseJSON (String t) = pure . encodeUtf8 $ t+ parseJSON v = typeMismatch "ByteString" v+ {-# INLINE parseJSON #-}++instance ToJSON LB.ByteString where+ toJSON = toJSON . strict+ {-# INLINE toJSON #-}++instance FromJSON LB.ByteString where+ parseJSON (String t) = pure . lazy $ t+ parseJSON v = typeMismatch "Lazy ByteString" v+ {-# INLINE parseJSON #-}++instance (ToJSON a) => ToJSON [a] where+ toJSON = Array . V.fromList . map toJSON+ {-# INLINE toJSON #-}++instance (FromJSON a) => FromJSON [a] where+ parseJSON (Array a) = mapM parseJSON (V.toList a)+ parseJSON v = typeMismatch "[a]" v+ {-# INLINE parseJSON #-}++instance (ToJSON a) => ToJSON (Vector a) where+ toJSON = Array . V.map toJSON+ {-# INLINE toJSON #-}++instance (FromJSON a) => FromJSON (Vector a) where+ parseJSON (Array a) = V.mapM parseJSON a+ parseJSON v = typeMismatch "Vector a" v+ {-# INLINE parseJSON #-}++vectorToJSON :: (VG.Vector v a, ToJSON a) => v a -> Value+vectorToJSON = Array . V.map toJSON . V.convert+{-# INLINE vectorToJSON #-}++vectorParseJSON :: (FromJSON a, VG.Vector w a) => String -> Value -> Parser (w a)+vectorParseJSON _ (Array a) = V.convert <$> V.mapM parseJSON a+vectorParseJSON s v = typeMismatch s v+{-# INLINE vectorParseJSON #-}++instance (Storable a, ToJSON a) => ToJSON (VS.Vector a) where+ toJSON = vectorToJSON++instance (Storable a, FromJSON a) => FromJSON (VS.Vector a) where+ parseJSON = vectorParseJSON "Data.Vector.Storable.Vector a"++instance (VP.Prim a, ToJSON a) => ToJSON (VP.Vector a) where+ toJSON = vectorToJSON++instance (VP.Prim a, FromJSON a) => FromJSON (VP.Vector a) where+ parseJSON = vectorParseJSON "Data.Vector.Primitive.Vector a"++instance (VG.Vector VU.Vector a, ToJSON a) => ToJSON (VU.Vector a) where+ toJSON = vectorToJSON++instance (VG.Vector VU.Vector a, FromJSON a) => FromJSON (VU.Vector a) where+ parseJSON = vectorParseJSON "Data.Vector.Unboxed.Vector a"++instance (ToJSON a) => ToJSON (Set.Set a) where+ toJSON = toJSON . Set.toList+ {-# INLINE toJSON #-}++instance (Ord a, FromJSON a) => FromJSON (Set.Set a) where+ parseJSON = fmap Set.fromList . parseJSON+ {-# INLINE parseJSON #-}++instance (ToJSON a) => ToJSON (HashSet.HashSet a) where+ toJSON = toJSON . HashSet.toList+ {-# INLINE toJSON #-}++instance (Eq a, Hashable a, FromJSON a) => FromJSON (HashSet.HashSet a) where+ parseJSON = fmap HashSet.fromList . parseJSON+ {-# INLINE parseJSON #-}++instance ToJSON IntSet.IntSet where+ toJSON = toJSON . IntSet.toList+ {-# INLINE toJSON #-}++instance FromJSON IntSet.IntSet where+ parseJSON = fmap IntSet.fromList . parseJSON+ {-# INLINE parseJSON #-}++instance ToJSON a => ToJSON (IntMap.IntMap a) where+ toJSON = toJSON . IntMap.toList+ {-# INLINE toJSON #-}++instance FromJSON a => FromJSON (IntMap.IntMap a) where+ parseJSON = fmap IntMap.fromList . parseJSON+ {-# INLINE parseJSON #-}++instance (ToJSON v) => ToJSON (M.Map Text v) where+ toJSON = Object . M.foldrWithKey (\k -> H.insert k . toJSON) H.empty+ {-# INLINE toJSON #-}++instance (FromJSON v) => FromJSON (M.Map Text v) where+ parseJSON (Object o) = H.foldrWithKey M.insert M.empty <$> traverse parseJSON o+ parseJSON v = typeMismatch "Map Text a" v++instance (ToJSON v) => ToJSON (M.Map LT.Text v) where+ toJSON = Object . mapHashKeyVal LT.toStrict toJSON++instance (FromJSON v) => FromJSON (M.Map LT.Text v) where+ parseJSON = fmap (hashMapKey LT.fromStrict) . parseJSON++instance (ToJSON v) => ToJSON (M.Map String v) where+ toJSON = Object . mapHashKeyVal pack toJSON++instance (FromJSON v) => FromJSON (M.Map String v) where+ parseJSON = fmap (hashMapKey unpack) . parseJSON++instance (ToJSON v) => ToJSON (M.Map B.ByteString v) where+ toJSON = Object . mapHashKeyVal decode toJSON++instance (FromJSON v) => FromJSON (M.Map B.ByteString v) where+ parseJSON = fmap (hashMapKey encodeUtf8) . parseJSON++instance (ToJSON v) => ToJSON (M.Map LB.ByteString v) where+ toJSON = Object . mapHashKeyVal strict toJSON++instance (FromJSON v) => FromJSON (M.Map LB.ByteString v) where+ parseJSON = fmap (hashMapKey lazy) . parseJSON++instance (ToJSON v) => ToJSON (H.HashMap Text v) where+ toJSON = Object . H.map toJSON+ {-# INLINE toJSON #-}++instance (FromJSON v) => FromJSON (H.HashMap Text v) where+ parseJSON (Object o) = traverse parseJSON o+ parseJSON v = typeMismatch "HashMap Text a" v++instance (ToJSON v) => ToJSON (H.HashMap LT.Text v) where+ toJSON = Object . mapKeyVal LT.toStrict toJSON++instance (FromJSON v) => FromJSON (H.HashMap LT.Text v) where+ parseJSON = fmap (mapKey LT.fromStrict) . parseJSON++instance (ToJSON v) => ToJSON (H.HashMap String v) where+ toJSON = Object . mapKeyVal pack toJSON++instance (FromJSON v) => FromJSON (H.HashMap String v) where+ parseJSON = fmap (mapKey unpack) . parseJSON++instance (ToJSON v) => ToJSON (H.HashMap B.ByteString v) where+ toJSON = Object . mapKeyVal decode toJSON++instance (FromJSON v) => FromJSON (H.HashMap B.ByteString v) where+ parseJSON = fmap (mapKey encodeUtf8) . parseJSON++instance (ToJSON v) => ToJSON (H.HashMap LB.ByteString v) where+ toJSON = Object . mapKeyVal strict toJSON++instance (FromJSON v) => FromJSON (H.HashMap LB.ByteString v) where+ parseJSON = fmap (mapKey lazy) . parseJSON++instance ToJSON Value where+ toJSON a = a+ {-# INLINE toJSON #-}++instance FromJSON Value where+ parseJSON a = pure a+ {-# INLINE parseJSON #-}++-- | A newtype wrapper for 'UTCTime' that uses the same non-standard+-- serialization format as Microsoft .NET, whose @System.DateTime@+-- type is by default serialized to JSON as in the following example:+--+-- > /Date(1302547608878)/+--+-- The number represents milliseconds since the Unix epoch.+newtype DotNetTime = DotNetTime {+ fromDotNetTime :: UTCTime+ } deriving (Eq, Ord, Read, Show, Typeable, FormatTime)++instance ToJSON DotNetTime where+ toJSON (DotNetTime t) =+ String (pack (secs ++ msecs ++ ")/"))+ where secs = formatTime defaultTimeLocale "/Date(%s" t+ msecs = take 3 $ formatTime defaultTimeLocale "%q" t+ {-# INLINE toJSON #-}++instance FromJSON DotNetTime where+ parseJSON (String t) =+ case parseTime defaultTimeLocale "/Date(%s%Q)/" (unpack t') of+ Just d -> pure (DotNetTime d)+ _ -> fail "could not parse .NET time"+ where (s,m) = T.splitAt (T.length t - 5) t+ t' = T.concat [s,".",m]+ parseJSON v = typeMismatch "DotNetTime" v+ {-# INLINE parseJSON #-}++instance ToJSON UTCTime where+ toJSON t = String (pack (take 23 str ++ "Z"))+ where str = formatTime defaultTimeLocale "%FT%T%Q" t+ {-# INLINE toJSON #-}++instance FromJSON UTCTime where+ parseJSON (String t) =+ case parseTime defaultTimeLocale "%FT%T%QZ" (unpack t) of+ Just d -> pure d+ _ -> fail "could not parse ISO-8601 date"+ parseJSON v = typeMismatch "UTCTime" v+ {-# INLINE parseJSON #-}++instance (ToJSON a, ToJSON b) => ToJSON (a,b) where+ toJSON (a,b) = Array $ V.create $ do+ mv <- VM.unsafeNew 2+ VM.unsafeWrite mv 0 (toJSON a)+ VM.unsafeWrite mv 1 (toJSON b)+ return mv+ {-# INLINE toJSON #-}++instance (FromJSON a, FromJSON b) => FromJSON (a,b) where+ parseJSON (Array ab)+ | n == 2 = (,) <$> parseJSON (V.unsafeIndex ab 0)+ <*> parseJSON (V.unsafeIndex ab 1)+ | otherwise = fail $ "cannot unpack array of length " +++ show n ++ " into a pair"+ where+ n = V.length ab+ parseJSON v = typeMismatch "(a,b)" v+ {-# INLINE parseJSON #-}++instance (ToJSON a, ToJSON b, ToJSON c) => ToJSON (a,b,c) where+ toJSON (a,b,c) = Array $ V.create $ do+ mv <- VM.unsafeNew 3+ VM.unsafeWrite mv 0 (toJSON a)+ VM.unsafeWrite mv 1 (toJSON b)+ VM.unsafeWrite mv 2 (toJSON c)+ return mv+ {-# INLINE toJSON #-}++instance (FromJSON a, FromJSON b, FromJSON c) => FromJSON (a,b,c) where+ parseJSON (Array abc)+ | n == 3 = (,,) <$> parseJSON (V.unsafeIndex abc 0)+ <*> parseJSON (V.unsafeIndex abc 1)+ <*> parseJSON (V.unsafeIndex abc 2)+ | otherwise = fail $ "cannot unpack array of length " +++ show n ++ " into a 3-tuple"+ where+ n = V.length abc+ parseJSON v = typeMismatch "(a,b,c)" v+ {-# INLINE parseJSON #-}++instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSON (a,b,c,d) where+ toJSON (a,b,c,d) = Array $ V.create $ do+ mv <- VM.unsafeNew 4+ VM.unsafeWrite mv 0 (toJSON a)+ VM.unsafeWrite mv 1 (toJSON b)+ VM.unsafeWrite mv 2 (toJSON c)+ VM.unsafeWrite mv 3 (toJSON d)+ return mv+ {-# INLINE toJSON #-}++instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d) => FromJSON (a,b,c,d) where+ parseJSON (Array abcd)+ | n == 4 = (,,,) <$> parseJSON (V.unsafeIndex abcd 0)+ <*> parseJSON (V.unsafeIndex abcd 1)+ <*> parseJSON (V.unsafeIndex abcd 2)+ <*> parseJSON (V.unsafeIndex abcd 3)+ | otherwise = fail $ "cannot unpack array of length " +++ show n ++ " into a 4-tuple"+ where+ n = V.length abcd+ parseJSON v = typeMismatch "(a,b,c,d)" v+ {-# INLINE parseJSON #-}++instance ToJSON a => ToJSON (Dual a) where+ toJSON = toJSON . getDual+ {-# INLINE toJSON #-}++instance FromJSON a => FromJSON (Dual a) where+ parseJSON = fmap Dual . parseJSON+ {-# INLINE parseJSON #-}++instance ToJSON a => ToJSON (First a) where+ toJSON = toJSON . getFirst+ {-# INLINE toJSON #-}++instance FromJSON a => FromJSON (First a) where+ parseJSON = fmap First . parseJSON+ {-# INLINE parseJSON #-}++instance ToJSON a => ToJSON (Last a) where+ toJSON = toJSON . getLast+ {-# INLINE toJSON #-}++instance FromJSON a => FromJSON (Last a) where+ parseJSON = fmap Last . parseJSON+ {-# INLINE parseJSON #-}++-- | Construct a 'Pair' from a key and a value.+(.=) :: ToJSON a => Text -> a -> Pair+name .= value = (name, toJSON value)+{-# INLINE (.=) #-}++-- | Convert a value from JSON, failing if the types do not match.+fromJSON :: (FromJSON a) => Value -> Result a+fromJSON = parse parseJSON+{-# INLINE fromJSON #-}++-- | Retrieve the value associated with the given key of an 'Object'.+-- The result is 'empty' if the key is not present or the value cannot+-- be converted to the desired type.+--+-- This accessor is appropriate if the key and value /must/ be present+-- in an object for it to be valid. If the key and value are+-- optional, use '(.:?)' instead.+(.:) :: (FromJSON a) => Object -> Text -> Parser a+obj .: key = case H.lookup key obj of+ Nothing -> fail $ "key " ++ show key ++ " not present"+ Just v -> parseJSON v+{-# INLINE (.:) #-}++-- | Retrieve the value associated with the given key of an 'Object'.+-- The result is 'Nothing' if the key is not present, or 'empty' if+-- the value cannot be converted to the desired type.+--+-- This accessor is most useful if the key and value can be absent+-- from an object without affecting its validity. If the key and+-- value are mandatory, use '(.:)' instead.+(.:?) :: (FromJSON a) => Object -> Text -> Parser (Maybe a)+obj .:? key = case H.lookup key obj of+ Nothing -> pure Nothing+ Just v -> parseJSON v+{-# INLINE (.:?) #-}++-- | Helper for use in combination with '.:?' to provide default+-- values for optional JSON object fields.+--+-- This combinator is most useful if the key and value can be absent+-- from an object without affecting its validity and we know a default+-- value to assign in that case. If the key and value are mandatory,+-- use '(.:)' instead.+-- +-- Example usage:+--+-- @ v1 <- o '.:?' \"opt_field_with_dfl\" .!= \"default_val\"+-- v2 <- o '.:' \"mandatory_field\"+-- v3 <- o '.:?' \"opt_field2\"+-- @+(.!=) :: Parser (Maybe a) -> a -> Parser a+pmval .!= val = fromMaybe val <$> pmval+{-# INLINE (.!=) #-}++-- | Fail parsing due to a type mismatch, with a descriptive message.+typeMismatch :: String -- ^ The name of the type you are trying to parse.+ -> Value -- ^ The actual value encountered.+ -> Parser a+typeMismatch expected actual =+ fail $ "when expecting a " ++ expected ++ ", encountered " ++ name +++ " instead"+ where+ name = case actual of+ Object _ -> "Object"+ Array _ -> "Array"+ String _ -> "String"+ Number _ -> "Number"+ Bool _ -> "Boolean"+ Null -> "Null"
+ Data/Aeson/Types/Generic.hs view
@@ -0,0 +1,284 @@+{-# LANGUAGE DefaultSignatures+ , EmptyDataDecls+ , FlexibleInstances+ , FunctionalDependencies+ , KindSignatures+ , OverlappingInstances+ , ScopedTypeVariables+ , TypeOperators+ , UndecidableInstances+ , ViewPatterns+ #-}++{-# OPTIONS_GHC -fno-warn-orphans #-}++-- |+-- Module: Data.Aeson.Types.Generic+-- Copyright: (c) 2011 MailRank, Inc.+-- License: Apache+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>+-- Stability: experimental+-- Portability: portable+--+-- Types for working with JSON data.++module Data.Aeson.Types.Generic ( ) where++import Control.Applicative+import Control.Monad.State.Strict+import Data.Bits (shiftR)+import Data.Aeson.Types.Class+import Data.Aeson.Types.Internal+import Data.Text (pack, unpack)+import Data.DList (DList, toList)+import Data.Monoid (mappend)+import GHC.Generics+import Control.Monad.ST (ST)+import qualified Data.HashMap.Strict as H+import qualified Data.Text as T+import qualified Data.Vector as V+import qualified Data.Vector.Mutable as VM++--------------------------------------------------------------------------------+-- Generic toJSON++instance (GToJSON a) => GToJSON (M1 i c a) where+ gToJSON = gToJSON . unM1+ {-# INLINE gToJSON #-}++instance (ToJSON a) => GToJSON (K1 i a) where+ gToJSON = toJSON . unK1+ {-# INLINE gToJSON #-}++instance GToJSON U1 where+ gToJSON _ = emptyArray+ {-# INLINE gToJSON #-}++instance (ConsToJSON a) => GToJSON (C1 c a) where+ gToJSON = consToJSON . unM1+ {-# INLINE gToJSON #-}++instance ( GProductToValues a, GProductToValues b+ , ProductSize a, ProductSize b) => GToJSON (a :*: b) where+ gToJSON p = Array $ V.create $ do+ mv <- VM.unsafeNew lenProduct+ gProductToValues mv 0 lenProduct p+ return mv+ where+ lenProduct = unTagged2 (productSize :: Tagged2 (a :*: b) Int)+ {-# INLINE gToJSON #-}++instance (GObject a, GObject b) => GToJSON (a :+: b) where+ gToJSON (L1 x) = Object $ gObject x+ gToJSON (R1 x) = Object $ gObject x+ {-# INLINE gToJSON #-}++--------------------------------------------------------------------------------++class ConsToJSON f where consToJSON :: f a -> Value+class ConsToJSON' b f where consToJSON' :: Tagged b (f a -> Value)++newtype Tagged s b = Tagged {unTagged :: b}++instance (IsRecord f b, ConsToJSON' b f) => ConsToJSON f where+ consToJSON = unTagged (consToJSON' :: Tagged b (f a -> Value))+ {-# INLINE consToJSON #-}++instance (GRecordToPairs f) => ConsToJSON' True f where+ consToJSON' = Tagged (object . toList . gRecordToPairs)+ {-# INLINE consToJSON' #-}++instance GToJSON f => ConsToJSON' False f where+ consToJSON' = Tagged gToJSON+ {-# INLINE consToJSON' #-}++--------------------------------------------------------------------------------++class GRecordToPairs f where+ gRecordToPairs :: f a -> DList Pair++instance (GRecordToPairs a, GRecordToPairs b) => GRecordToPairs (a :*: b) where+ gRecordToPairs (a :*: b) = gRecordToPairs a `mappend` gRecordToPairs b+ {-# INLINE gRecordToPairs #-}++instance (Selector s, GToJSON a) => GRecordToPairs (S1 s a) where+ gRecordToPairs m1 = pure (pack (selName m1), gToJSON (unM1 m1))+ {-# INLINE gRecordToPairs #-}++--------------------------------------------------------------------------------++class GProductToValues f where+ gProductToValues :: VM.MVector s Value -> Int -> Int -> f a -> ST s ()++instance (GProductToValues a, GProductToValues b) => GProductToValues (a :*: b) where+ gProductToValues mv ix len (a :*: b) = do gProductToValues mv ix lenL a+ gProductToValues mv ixR lenR b+ where+ lenL = len `shiftR` 1+ ixR = ix + lenL+ lenR = len - lenL+ {-# INLINE gProductToValues #-}++instance (GToJSON a) => GProductToValues a where+ gProductToValues mv ix _ = VM.unsafeWrite mv ix . gToJSON+ {-# INLINE gProductToValues #-}++--------------------------------------------------------------------------------++class GObject f where+ gObject :: f a -> Object++instance (GObject a, GObject b) => GObject (a :+: b) where+ gObject (L1 x) = gObject x+ gObject (R1 x) = gObject x+ {-# INLINE gObject #-}++instance (Constructor c, GToJSON a, ConsToJSON a) => GObject (C1 c a) where+ gObject = H.singleton (pack $ conName (undefined :: t c a p)) . gToJSON+ {-# INLINE gObject #-}++--------------------------------------------------------------------------------+-- Generic parseJSON++instance (GFromJSON a) => GFromJSON (M1 i c a) where+ gParseJSON = fmap M1 . gParseJSON+ {-# INLINE gParseJSON #-}++instance (FromJSON a) => GFromJSON (K1 i a) where+ gParseJSON = fmap K1 . parseJSON+ {-# INLINE gParseJSON #-}++instance GFromJSON U1 where+ gParseJSON v+ | isEmptyArray v = pure U1+ | otherwise = typeMismatch "unit constructor (U1)" v+ {-# INLINE gParseJSON #-}++instance (ConsFromJSON a) => GFromJSON (C1 c a) where+ gParseJSON = fmap M1 . consParseJSON+ {-# INLINE gParseJSON #-}++instance ( GFromProduct a, GFromProduct b+ , ProductSize a, ProductSize b) => GFromJSON (a :*: b) where+ gParseJSON (Array arr)+ | lenArray == lenProduct = gParseProduct arr 0 lenProduct+ | otherwise =+ fail $ "When expecting a product of " ++ show lenProduct +++ " values, encountered an Array of " ++ show lenArray +++ " elements instead"+ where+ lenArray = V.length arr+ lenProduct = unTagged2 (productSize :: Tagged2 (a :*: b) Int)++ gParseJSON v = typeMismatch "product (:*:)" v+ {-# INLINE gParseJSON #-}++instance (GFromSum a, GFromSum b) => GFromJSON (a :+: b) where+ gParseJSON (Object (H.toList -> [keyVal@(key, _)])) =+ case gParseSum keyVal of+ Nothing -> notFound $ unpack key+ Just p -> p+ gParseJSON v = typeMismatch "sum (:+:)" v+ {-# INLINE gParseJSON #-}++notFound :: String -> Parser a+notFound key = fail $ "The key \"" ++ key ++ "\" was not found"+{-# INLINE notFound #-}++--------------------------------------------------------------------------------++class ConsFromJSON f where consParseJSON :: Value -> Parser (f a)+class ConsFromJSON' b f where consParseJSON' :: Tagged b (Value -> Parser (f a))++instance (IsRecord f b, ConsFromJSON' b f) => ConsFromJSON f where+ consParseJSON = unTagged (consParseJSON' :: Tagged b (Value -> Parser (f a)))+ {-# INLINE consParseJSON #-}++instance (GFromRecord f) => ConsFromJSON' True f where+ consParseJSON' = Tagged parseRecord+ where+ parseRecord (Object obj) = gParseRecord obj+ parseRecord v = typeMismatch "record (:*:)" v+ {-# INLINE consParseJSON' #-}++instance (GFromJSON f) => ConsFromJSON' False f where+ consParseJSON' = Tagged gParseJSON+ {-# INLINE consParseJSON' #-}++--------------------------------------------------------------------------------++class GFromRecord f where+ gParseRecord :: Object -> Parser (f a)++instance (GFromRecord a, GFromRecord b) => GFromRecord (a :*: b) where+ gParseRecord obj = (:*:) <$> gParseRecord obj <*> gParseRecord obj+ {-# INLINE gParseRecord #-}++instance (Selector s, GFromJSON a) => GFromRecord (S1 s a) where+ gParseRecord = maybe (notFound key) gParseJSON . H.lookup (T.pack key)+ where+ key = selName (undefined :: t s a p)+ {-# INLINE gParseRecord #-}++--------------------------------------------------------------------------------++class ProductSize f where+ productSize :: Tagged2 f Int++newtype Tagged2 (s :: * -> *) b = Tagged2 {unTagged2 :: b}++instance (ProductSize a, ProductSize b) => ProductSize (a :*: b) where+ productSize = Tagged2 $ unTagged2 (productSize :: Tagged2 a Int) ++ unTagged2 (productSize :: Tagged2 b Int)++instance ProductSize (S1 s a) where+ productSize = Tagged2 1++--------------------------------------------------------------------------------++class GFromProduct f where+ gParseProduct :: Array -> Int -> Int -> Parser (f a)++instance (GFromProduct a, GFromProduct b) => GFromProduct (a :*: b) where+ gParseProduct arr ix len = (:*:) <$> gParseProduct arr ix lenL+ <*> gParseProduct arr ixR lenR+ where+ lenL = len `shiftR` 1+ ixR = ix + lenL+ lenR = len - lenL+ {-# INLINE gParseProduct #-}++instance (GFromJSON a) => GFromProduct (S1 s a) where+ gParseProduct arr ix _ = gParseJSON $ V.unsafeIndex arr ix+ {-# INLINE gParseProduct #-}++--------------------------------------------------------------------------------++class GFromSum f where+ gParseSum :: Pair -> Maybe (Parser (f a))++instance (GFromSum a, GFromSum b) => GFromSum (a :+: b) where+ gParseSum keyVal = (fmap L1 <$> gParseSum keyVal) <|>+ (fmap R1 <$> gParseSum keyVal)+ {-# INLINE gParseSum #-}++instance (Constructor c, GFromJSON a, ConsFromJSON a) => GFromSum (C1 c a) where+ gParseSum (key, value)+ | key == pack (conName (undefined :: t c a p)) = Just $ gParseJSON value+ | otherwise = Nothing+ {-# INLINE gParseSum #-}++--------------------------------------------------------------------------------++class IsRecord (f :: * -> *) b | f -> b++data True+data False++instance (IsRecord f b) => IsRecord (f :*: g) b+instance IsRecord (M1 S NoSelector f) False+instance (IsRecord f b) => IsRecord (M1 S c f) b+instance IsRecord (K1 i c) True+instance IsRecord U1 False++--------------------------------------------------------------------------------
+ Data/Aeson/Types/Internal.hs view
@@ -0,0 +1,223 @@+{-# LANGUAGE CPP, DeriveDataTypeable, Rank2Types #-}++-- |+-- Module: Data.Aeson.Types.Internal+-- Copyright: (c) 2011 Bryan O'Sullivan+-- (c) 2011 MailRank, Inc.+-- License: Apache+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>+-- Stability: experimental+-- Portability: portable+--+-- Types for working with JSON data.++module Data.Aeson.Types.Internal+ (+ -- * Core JSON types+ Value(..)+ , Array+ , emptyArray, isEmptyArray+ , Pair+ , Object+ , emptyObject+ -- * Type conversion+ , Parser+ , Result(..)+ , parse+ , parseEither+ , parseMaybe+ -- * Constructors and accessors+ , object+ ) where++import Control.Applicative+import Control.DeepSeq (NFData(..))+import Control.Monad.State.Strict+import Data.Attoparsec.Char8 (Number(..))+import Data.Hashable (Hashable(..))+import Data.HashMap.Strict (HashMap)+import Data.Monoid (Monoid(..))+import Data.String (IsString(..))+import Data.Text (Text, pack)+import Data.Typeable (Typeable)+import Data.Vector (Vector)+import qualified Data.HashMap.Strict as H+import qualified Data.Vector as V++-- | The result of running a 'Parser'.+data Result a = Error String+ | Success a+ deriving (Eq, Show, Typeable)++instance (NFData a) => NFData (Result a) where+ rnf (Success a) = rnf a+ rnf (Error err) = rnf err++instance Functor Result where+ fmap f (Success a) = Success (f a)+ fmap _ (Error err) = Error err+ {-# INLINE fmap #-}++instance Monad Result where+ return = Success+ {-# INLINE return #-}+ Success a >>= k = k a+ Error err >>= _ = Error err+ {-# INLINE (>>=) #-}++instance Applicative Result where+ pure = return+ {-# INLINE pure #-}+ (<*>) = ap+ {-# INLINE (<*>) #-}++instance MonadPlus Result where+ mzero = fail "mzero"+ {-# INLINE mzero #-}+ mplus a@(Success _) _ = a+ mplus _ b = b+ {-# INLINE mplus #-}++instance Alternative Result where+ empty = mzero+ {-# INLINE empty #-}+ (<|>) = mplus+ {-# INLINE (<|>) #-}++instance Monoid (Result a) where+ mempty = fail "mempty"+ {-# INLINE mempty #-}+ mappend = mplus+ {-# INLINE mappend #-}++-- | Failure continuation.+type Failure f r = String -> f r+-- | Success continuation.+type Success a f r = a -> f r++-- | A continuation-based parser type.+newtype Parser a = Parser {+ runParser :: forall f r.+ Failure f r+ -> Success a f r+ -> f r+ }++instance Monad Parser where+ m >>= g = Parser $ \kf ks -> let ks' a = runParser (g a) kf ks+ in runParser m kf ks'+ {-# INLINE (>>=) #-}+ return a = Parser $ \_kf ks -> ks a+ {-# INLINE return #-}+ fail msg = Parser $ \kf _ks -> kf msg+ {-# INLINE fail #-}++instance Functor Parser where+ fmap f m = Parser $ \kf ks -> let ks' a = ks (f a)+ in runParser m kf ks'+ {-# INLINE fmap #-}++instance Applicative Parser where+ pure = return+ {-# INLINE pure #-}+ (<*>) = apP+ {-# INLINE (<*>) #-}++instance Alternative Parser where+ empty = fail "empty"+ {-# INLINE empty #-}+ (<|>) = mplus+ {-# INLINE (<|>) #-}++instance MonadPlus Parser where+ mzero = fail "mzero"+ {-# INLINE mzero #-}+ mplus a b = Parser $ \kf ks -> let kf' _ = runParser b kf ks+ in runParser a kf' ks+ {-# INLINE mplus #-}++instance Monoid (Parser a) where+ mempty = fail "mempty"+ {-# INLINE mempty #-}+ mappend = mplus+ {-# INLINE mappend #-}++apP :: Parser (a -> b) -> Parser a -> Parser b+apP d e = do+ b <- d+ a <- e+ return (b a)+{-# INLINE apP #-}++-- | A JSON \"object\" (key\/value map).+type Object = HashMap Text Value++-- | A JSON \"array\" (sequence).+type Array = Vector Value++-- | A JSON value represented as a Haskell value.+data Value = Object !Object+ | Array !Array+ | String !Text+ | Number !Number+ | Bool !Bool+ | Null+ deriving (Eq, Show, Typeable)++instance NFData Value where+ rnf (Object o) = rnf o+ rnf (Array a) = V.foldl' (\x y -> rnf y `seq` x) () a+ rnf (String s) = rnf s+ rnf (Number n) = case n of I i -> rnf i; D d -> rnf d+ rnf (Bool b) = rnf b+ rnf Null = ()++instance IsString Value where+ fromString = String . pack+ {-# INLINE fromString #-}++instance Hashable Value where+ hash (Object o) = H.foldl' hashWithSalt 0 o+ hash (Array a) = V.foldl' hashWithSalt 1 a+ hash (String s) = 2 `hashWithSalt` s+ hash (Number n) = 3 `hashWithSalt` case n of I i -> hash i; D d -> hash d+ hash (Bool b) = 4 `hashWithSalt` b+ hash Null = 5++-- | The empty array.+emptyArray :: Value+emptyArray = Array V.empty++-- | Determines if the 'Value' is an empty 'Array'.+-- Note that: @isEmptyArray 'emptyArray'@.+isEmptyArray :: Value -> Bool+isEmptyArray (Array arr) = V.null arr+isEmptyArray _ = False++-- | The empty object.+emptyObject :: Value+emptyObject = Object H.empty++-- | Run a 'Parser'.+parse :: (a -> Parser b) -> a -> Result b+parse m v = runParser (m v) Error Success+{-# INLINE parse #-}++-- | Run a 'Parser' with a 'Maybe' result type.+parseMaybe :: (a -> Parser b) -> a -> Maybe b+parseMaybe m v = runParser (m v) (const Nothing) Just+{-# INLINE parseMaybe #-}++-- | Run a 'Parser' with an 'Either' result type.+parseEither :: (a -> Parser b) -> a -> Either String b+parseEither m v = runParser (m v) Left Right++-- | A key\/value pair for an 'Object'.+type Pair = (Text, Value)++{-# INLINE parseEither #-}+-- | Create a 'Value' from a list of name\/value 'Pair's. If duplicate+-- keys arise, earlier keys and their associated values win.+object :: [Pair] -> Value+object = Object . H.fromList+{-# INLINE object #-}
README.markdown view
@@ -2,28 +2,18 @@ aeson is a fast Haskell library for working with JSON data. -# Important note for users of GHCi and Template Haskell -This package depends on the [blaze-textual-package](http://hackage.haskell.org/package/blaze-textual) , and is-unfortunately subject to crashes if you use GHCi or Template Haskell-in your work.--For complete details, including a workaround, see the writeup in [the-blaze-textual-README](https://github.com/mailrank/blaze-textual#readme).- # Join in! We are happy to receive bug reports, fixes, documentation enhancements, and other improvements. Please report bugs via the-[github issue tracker](http://github.com/mailrank/aeson/issues).+[github issue tracker](http://github.com/bos/aeson/issues). -Master [git repository](http://github.com/mailrank/aeson):+Master [git repository](http://github.com/bos/aeson): -* `git clone git://github.com/mailrank/aeson.git`+* `git clone git://github.com/bos/aeson.git` There's also a [Mercurial mirror](http://bitbucket.org/bos/aeson): @@ -31,8 +21,8 @@ (You can create and contribute changes using either git or Mercurial.) -Authors-------- +# Authors+ This library is written and maintained by Bryan O'Sullivan,-<bos@mailrank.com>.+<bos@serpentine.com>.
aeson.cabal view
@@ -1,26 +1,33 @@ name: aeson-version: 0.3.2.14+version: 0.4.0.0 license: BSD3 license-file: LICENSE category: Text, Web, JSON-copyright: Copyright 2011 MailRank, Inc.-author: Bryan O'Sullivan <bos@mailrank.com>-maintainer: Bryan O'Sullivan <bos@mailrank.com>+copyright: (c) 2011 Bryan O'Sullivan+ (c) 2011 MailRank, Inc.+author: Bryan O'Sullivan <bos@serpentine.com>+maintainer: Bryan O'Sullivan <bos@serpentine.com> stability: experimental-tested-with: GHC == 6.12.3+tested-with: GHC == 6.12.3, GHC == 7.0.4, GHC == 7.2.2 synopsis: Fast JSON parsing and encoding cabal-version: >= 1.8-homepage: http://github.com/mailrank/aeson-bug-reports: http://github.com/mailrank/aeson/issues+homepage: https://github.com/bos/aeson+bug-reports: https://github.com/bos/aeson/issues build-type: Simple description: A JSON parsing and encoding library optimized for ease of use and high performance. .+ To get started, see the documentation for the @Data.Aeson@ module+ below.+ .+ For release notes, see+ <https://github.com/bos/aeson/blob/master/release-notes.markdown>+ . /Note/: if you use GHCi or Template Haskell, please see the @README@ file for important details about building this package, and other packages that depend on it:- <https://github.com/mailrank/aeson#readme>+ <https://github.com/bos/aeson#readme> . Parsing performance on a late 2010 MacBook Pro (2.66GHz Core i7), for mostly-English tweets from Twitter's JSON search API:@@ -77,26 +84,13 @@ extra-source-files: README.markdown- benchmarks/AesonEncode.hs- benchmarks/AesonParse.hs- benchmarks/JsonParse.hs+ benchmarks/*.hs+ benchmarks/*.py benchmarks/Makefile- benchmarks/ReadFile.hs- benchmarks/parse.py- benchmarks/json-data/example.json- benchmarks/json-data/integers.json- benchmarks/json-data/jp10.json- benchmarks/json-data/jp50.json- benchmarks/json-data/jp100.json- benchmarks/json-data/numbers.json- benchmarks/json-data/twitter1.json- benchmarks/json-data/twitter10.json- benchmarks/json-data/twitter20.json- benchmarks/json-data/twitter50.json- benchmarks/json-data/twitter100.json- tests/Makefile+ benchmarks/json-data/*.json+ examples/*.hs+ release-notes.markdown tests/Properties.hs- examples/Demo.hs flag developer description: operate in developer mode@@ -113,7 +107,16 @@ other-modules: Data.Aeson.Functions+ Data.Aeson.Parser.Internal+ Data.Aeson.Types.Class+ Data.Aeson.Types.Internal + if impl(ghc >= 7.2.1)+ cpp-options: -DGENERICS+ build-depends: ghc-prim >= 0.2, dlist >= 0.2+ other-modules:+ Data.Aeson.Types.Generic+ build-depends: attoparsec >= 0.8.6.1, base == 4.*,@@ -126,25 +129,42 @@ mtl, old-locale, syb,- text >= 0.11.0.2, template-haskell >= 2.4,+ text >= 0.11.0.2, time, unordered-containers >= 0.1.3.0,- vector >= 0.7+ vector >= 0.7.1 if flag(developer) ghc-options: -Werror ghc-prof-options: -auto-all - if(impl(ghc >= 7.2.1))- cpp-options: -DDEFAULT_SIGNATURES- ghc-options: -Wall +test-suite tests+ type: exitcode-stdio-1.0+ hs-source-dirs: tests+ main-is: Properties.hs++ ghc-options:+ -Wall -threaded -rtsopts++ build-depends:+ QuickCheck,+ aeson,+ attoparsec,+ base,+ containers,+ bytestring,+ template-haskell,+ test-framework,+ test-framework-quickcheck2,+ text+ source-repository head type: git- location: http://github.com/mailrank/aeson+ location: git://github.com/bos/aeson.git source-repository head type: mercurial- location: http://bitbucket.org/bos/aeson+ location: https://bitbucket.org/bos/aeson
+ benchmarks/AesonCompareAutoInstances.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE DeriveDataTypeable, DeriveGeneric, TemplateHaskell #-}++module Main where++--------------------------------------------------------------------------------++import Criterion.Main++import Control.DeepSeq (NFData, rnf, deepseq)++import Data.Typeable (Typeable)+import Data.Data (Data)+import GHC.Generics (Generic)++import Data.Aeson.Types+import Data.Aeson.TH (mkToJSON, mkParseJSON)+import qualified Data.Aeson.Generic as G (fromJSON, toJSON)++--------------------------------------------------------------------------------++-- Taken from the documentation of Data.Aeson.TH:+data D a = Nullary+ | Unary Int+ | Product String Char a+ | Record { testOne :: Double+ , testTwo :: Bool+ , testThree :: D a+ } deriving (Show, Eq, Generic, Data, Typeable)++instance NFData a => NFData (D a) where+ rnf Nullary = ()+ rnf (Unary n) = rnf n+ rnf (Product s c x) = s `deepseq` c `deepseq` rnf x+ rnf (Record d b y) = d `deepseq` b `deepseq` rnf y++type T = D (D (D ()))++d :: T+d = Record+ { testOne = 1234.56789+ , testTwo = True+ , testThree = Product "Hello World!" 'a' $+ Record+ { testOne = 9876.54321+ , testTwo = False+ , testThree = Product "Yeehaa!!!" '\n' Nullary+ }+ }++instance ToJSON a => ToJSON (D a)+instance FromJSON a => FromJSON (D a)++thDToJSON :: ToJSON a => D a -> Value+thDToJSON = $(mkToJSON id ''D)++thDParseJSON :: FromJSON a => Value -> Parser (D a)+thDParseJSON = $(mkParseJSON id ''D)++thDFromJSON :: FromJSON a => Value -> Result (D a)+thDFromJSON = parse thDParseJSON++--------------------------------------------------------------------------------++data BigRecord = BigRecord+ { field01 :: !Int, field02 :: !Int, field03 :: !Int, field04 :: !Int, field05 :: !Int+ , field06 :: !Int, field07 :: !Int, field08 :: !Int, field09 :: !Int, field10 :: !Int+ , field11 :: !Int, field12 :: !Int, field13 :: !Int, field14 :: !Int, field15 :: !Int+ , field16 :: !Int, field17 :: !Int, field18 :: !Int, field19 :: !Int, field20 :: !Int+ , field21 :: !Int, field22 :: !Int, field23 :: !Int, field24 :: !Int, field25 :: !Int+ } deriving (Show, Eq, Generic, Data, Typeable)++instance NFData BigRecord++bigRecord = BigRecord 1 2 3 4 5+ 6 7 8 9 10+ 11 12 13 14 15+ 16 17 18 19 20+ 21 22 23 24 25++instance ToJSON BigRecord+instance FromJSON BigRecord++thBigRecordToJSON :: BigRecord -> Value+thBigRecordToJSON = $(mkToJSON id ''BigRecord)++thBigRecordParseJSON :: Value -> Parser BigRecord+thBigRecordParseJSON = $(mkParseJSON id ''BigRecord)++thBigRecordFromJSON :: Value -> Result BigRecord+thBigRecordFromJSON = parse thBigRecordParseJSON++--------------------------------------------------------------------------------++data BigProduct = BigProduct+ !Int !Int !Int !Int !Int+ !Int !Int !Int !Int !Int+ !Int !Int !Int !Int !Int+ !Int !Int !Int !Int !Int+ !Int !Int !Int !Int !Int+ deriving (Show, Eq, Generic, Data, Typeable)++instance NFData BigProduct++bigProduct = BigProduct 1 2 3 4 5+ 6 7 8 9 10+ 11 12 13 14 15+ 16 17 18 19 20+ 21 22 23 24 25++instance ToJSON BigProduct+instance FromJSON BigProduct++thBigProductToJSON :: BigProduct -> Value+thBigProductToJSON = $(mkToJSON id ''BigProduct)++thBigProductParseJSON :: Value -> Parser BigProduct+thBigProductParseJSON = $(mkParseJSON id ''BigProduct)++thBigProductFromJSON :: Value -> Result BigProduct+thBigProductFromJSON = parse thBigProductParseJSON++--------------------------------------------------------------------------------++data BigSum = F01 | F02 | F03 | F04 | F05+ | F06 | F07 | F08 | F09 | F10+ | F11 | F12 | F13 | F14 | F15+ | F16 | F17 | F18 | F19 | F20+ | F21 | F22 | F23 | F24 | F25+ deriving (Show, Eq, Generic, Data, Typeable)++instance NFData BigSum++bigSum = F25++instance ToJSON BigSum+instance FromJSON BigSum++thBigSumToJSON :: BigSum -> Value+thBigSumToJSON = $(mkToJSON id ''BigSum)++thBigSumParseJSON :: Value -> Parser BigSum+thBigSumParseJSON = $(mkParseJSON id ''BigSum)++thBigSumFromJSON :: Value -> Result BigSum+thBigSumFromJSON = parse thBigSumParseJSON++--------------------------------------------------------------------------------++type FJ a = Value -> Result a++main :: IO ()+main = defaultMain+ [ let v = thDToJSON d+ in d `deepseq` v `deepseq`+ bgroup "D"+ [ group "toJSON" (nf thDToJSON d)+ (nf G.toJSON d)+ (nf toJSON d)+ , group "fromJSON" (nf (thDFromJSON :: FJ T) v)+ (nf (G.fromJSON :: FJ T) v)+ (nf (fromJSON :: FJ T) v)+ ]+ , let v = thBigRecordToJSON bigRecord+ in bigRecord `deepseq` v `deepseq`+ bgroup "BigRecord"+ [ group "toJSON" (nf thBigRecordToJSON bigRecord)+ (nf G.toJSON bigRecord)+ (nf toJSON bigRecord)+ , group "fromJSON" (nf (thBigRecordFromJSON :: FJ BigRecord) v)+ (nf (G.fromJSON :: FJ BigRecord) v)+ (nf (fromJSON :: FJ BigRecord) v)+ ]+ , let v = thBigProductToJSON bigProduct+ in bigProduct `deepseq` v `deepseq`+ bgroup "BigProduct"+ [ group "toJSON" (nf thBigProductToJSON bigProduct)+ (nf G.toJSON bigProduct)+ (nf toJSON bigProduct)+ , group "fromJSON" (nf (thBigProductFromJSON :: FJ BigProduct) v)+ (nf (G.fromJSON :: FJ BigProduct) v)+ (nf (fromJSON :: FJ BigProduct) v)+ ]+ , let v = thBigSumToJSON bigSum+ in bigSum `deepseq` v `deepseq`+ bgroup "BigSum"+ [ group "toJSON" (nf thBigSumToJSON bigSum)+ (nf G.toJSON bigSum)+ (nf toJSON bigSum)+ , group "fromJSON" (nf (thBigSumFromJSON :: FJ BigSum) v)+ (nf (G.fromJSON :: FJ BigSum) v)+ (nf (fromJSON :: FJ BigSum) v)+ ]+ ]++group n th syb gen = bgroup n [ bench "th" th+ , bench "syb" syb+ , bench "generic" gen+ ]
+ benchmarks/AesonTuples.hs view
@@ -0,0 +1,45 @@+module Main where++--------------------------------------------------------------------------------++import Criterion.Main+import Control.DeepSeq (deepseq)+import Data.Aeson++--------------------------------------------------------------------------------++type FJ a = Value -> Result a++type T2 = (Int, Int)+type T3 = (Int, Int, Int)+type T4 = (Int, Int, Int, Int)++t2 :: T2+t2 = (1, 2)++t3 :: T3+t3 = (1, 2, 3)++t4 :: T4+t4 = (1, 2, 3, 4)++main :: IO ()+main = let v2 = toJSON t2+ v3 = toJSON t3+ v4 = toJSON t4+ in t2 `deepseq` t3 `deepseq` t4 `deepseq`+ v2 `deepseq` v3 `deepseq` v4 `deepseq`+ defaultMain+ [ bgroup "t2"+ [ bench "toJSON" (nf toJSON t2)+ , bench "fromJSON" (nf (fromJSON :: FJ T2) v2)+ ]+ , bgroup "t3"+ [ bench "toJSON" (nf toJSON t3)+ , bench "fromJSON" (nf (fromJSON :: FJ T3) v3)+ ]+ , bgroup "t4"+ [ bench "toJSON" (nf toJSON t4)+ , bench "fromJSON" (nf (fromJSON :: FJ T4) v4)+ ]+ ]
benchmarks/Makefile view
@@ -1,7 +1,7 @@ ghc := ghc ghcflags := -O -binaries := AesonParse AesonEncode JsonParse+binaries := AesonParse AesonEncode JsonParse AesonCompareAutoInstances all: $(binaries) $(binaries:%=%_p)
+ benchmarks/bench-parse.py view
@@ -0,0 +1,55 @@+#!/usr/bin/env python++import os, re, subprocess, sys++result_re = re.compile(r'^\s*(\d+) good, (\d+\.\d+)s$', re.M)++if len(sys.argv) > 1:+ parser_exe = sys.argv[1]+else:+ parser_exe = './AesonParse'++def run(count, filename):+ print ' %s :: %s times' % (filename, count)+ p = subprocess.Popen([parser_exe, str(count), filename],+ stdout=subprocess.PIPE)+ output = p.stdout.read()+ p.wait()+ m = result_re.search(output)+ if not m:+ print >> sys.stderr, 'run gave confusing output!?'+ sys.stderr.write(output)+ return+ else:+ #sys.stdout.write(output)+ pass+ good, elapsed = m.groups()+ good, elapsed = int(good), float(elapsed)+ st = os.stat(filename)+ parses_per_second = good / elapsed+ mb_per_second = st.st_size * parses_per_second / 1048576+ print (' %.3f seconds, %d parses/sec, %.3f MB/sec' %+ (elapsed, parses_per_second, mb_per_second))+ return parses_per_second, mb_per_second, st.st_size, elapsed++def runtimes(count, filename, times=1):+ for i in xrange(times):+ yield run(count, filename)++info = '''+json-data/twitter1.json 60000+json-data/twitter10.json 13000+json-data/twitter20.json 7500+json-data/twitter50.json 2500+json-data/twitter100.json 1000+json-data/jp10.json 4000+json-data/jp50.json 1200+json-data/jp100.json 700+'''++for i in info.strip().splitlines():+ name, count = i.split()+ best = sorted(runtimes(int(count), name, times=3), reverse=True)[0]+ parses_per_second, mb_per_second, size, elapsed = best+ print ('%.1f KB: %d msg\\/sec (%.1f MB\\/sec)' %+ (size / 1024.0, int(round(parses_per_second)), mb_per_second))
+ benchmarks/encode.py view
@@ -0,0 +1,14 @@+#!/usr/bin/env python++import json, sys, time++count = int(sys.argv[1])++for n in sys.argv[2:]:+ print '%s:' % n+ obj = json.load(open(n))+ start = time.time()+ for i in xrange(count):+ json.dumps(obj)+ end = time.time()+ print ' ', end - start
− examples/Demo.hs
@@ -1,70 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}---- Simplest example of parsing and encoding JSON with Aeson.---- Above, we enable OverloadedStrings to allow a literal string (e.g. "name")--- to be automatically converted to Data.Text.--- This is useful when using Aeson's functions such as (.:) which expect Text.--- Without it we'd need to use pack.--import Data.Aeson-import qualified Data.Aeson.Types as T--import Data.Attoparsec (parse, Result(..))-import Data.Text (Text)-import Control.Applicative ((<$>))-import Control.Monad (mzero)-import qualified Data.ByteString.Char8 as BS--- Aeson's "encode" to JSON generates lazy bytestrings-import qualified Data.ByteString.Lazy.Char8 as BSL---- In main we'll parse a JSON message into a Msg and display that,--- then we'll encode a different Msg as JSON, and display it.-main ::IO ()-main = do- print $ parseMsgFromString exampleJSONMessage- let reply = Msg "hello Aeson!"- putStrLn $ "Encoded reply: " ++ BSL.unpack (encode reply)---- this is the type we'll be converting to and from JSON-data Msg = Msg Text deriving (Show)---- here's how we should parse JSON and construct a Msg-instance FromJSON Msg where- parseJSON (Object v) = Msg <$> v .: "message"- parseJSON _ = mzero---- here's how we should encode a Msg as JSON-instance ToJSON Msg where- toJSON (Msg s) = object [ "message" .= s]---- Here's one way to actually run the parsers.------ Note that we do two parses:--- once into JSON then one more into our final type.--- There are a number of choices when dealing with parse failures.--- Here we've chosen to parse to Maybe Msg, and a Nothing will be returned--- if parseJSON fails. (More informative options are available.)------ This should take us (depending on success or failure)--- from {"message": "hello world"} to Just (Msg "hello world")--- or to Nothing------ Note also that we have not checked here that the input has been completely--- consumed, so:--- {"message": "hello world"} foo BIG mistake--- would yield the same successfully translated message!--- We could look in "rest" for the remainder.-parseMsgFromString :: String -> Maybe Msg-parseMsgFromString s =- let bs = BS.pack s- in case parse json bs of- (Done rest r) -> T.parseMaybe parseJSON r :: Maybe Msg- _ -> Nothing---- Here's the example JSON message we're going to try to parse:--- {"message": "hello world"}--- It's a JSON object with a single pair, having key 'message', and a string value.--- It could have more fields and structure, but that's all we're going to parse out of it.-exampleJSONMessage :: String-exampleJSONMessage = "{\"message\":\"hello world\"}"
+ examples/Generic.hs view
@@ -0,0 +1,38 @@+-- This example is basically the same as in Simplest.hs, only it uses+-- GHC's builtin generics instead of explicit instances of ToJSON and+-- FromJSON.++-- This example only works with GHC 7.2 or newer, as it uses the+-- datatype-generic programming machinery introduced in 7.2.++-- We enable the DeriveGeneric language extension so that GHC can+-- automatically derive the Generic class for us.++{-# LANGUAGE DeriveGeneric #-}++{-# LANGUAGE OverloadedStrings #-}++import Data.Aeson (FromJSON, ToJSON, decode, encode)+import qualified Data.ByteString.Lazy.Char8 as BL++-- To decode or encode a value using the generic machinery, we must+-- make the type an instance of the Generic class.++import GHC.Generics (Generic)++data Coord = Coord { x :: Double, y :: Double }+ deriving (Show, Generic)++-- While we still have to declare our type as instances of FromJSON+-- and ToJSON, we do *not* need to provide bodies for the instances.+-- Default versions will be supplied for us.++instance FromJSON Coord+instance ToJSON Coord++main :: IO ()+main = do+ let req = decode "{\"x\":3.0,\"y\":-1.0}" :: Maybe Coord+ print req+ let reply = Coord 123.4 20+ BL.putStrLn (encode reply)
+ examples/GenericSYB.hs view
@@ -0,0 +1,30 @@+-- This example is basically the same as in Simplest.hs, only it uses+-- SYB generics instead of explicit instances of ToJSON and FromJSON.++-- This mechanism is much slower than the newer generics mechanism+-- demonstrated in Generic.hs, but it works on versions of GHC older+-- than 7.2.++-- We enable the DeriveDataTypeable language extension so that GHC can+-- automatically derive the Typeable and Data classes for us.++{-# LANGUAGE DeriveDataTypeable #-}++{-# LANGUAGE OverloadedStrings #-}++import Data.Data (Typeable, Data)+import Data.Aeson.Generic (decode, encode)+import qualified Data.ByteString.Lazy.Char8 as BL++-- To decode or encode a value using the generic machinery, we must+-- make the type an instance of the Typeable and Data classes.++data Coord = Coord { x :: Double, y :: Double }+ deriving (Show, Typeable, Data)++main :: IO ()+main = do+ let req = decode "{\"x\":3.0,\"y\":-1.0}" :: Maybe Coord+ print req+ let reply = Coord 123.4 20+ BL.putStrLn (encode reply)
+ examples/Simplest.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE OverloadedStrings #-}++import Control.Applicative ((<$>), (<*>), empty)+import Data.Aeson+import qualified Data.ByteString.Lazy.Char8 as BL++data Coord = Coord { x :: Double, y :: Double }+ deriving (Show)++-- A ToJSON instance allows us to encode a value as JSON.++instance ToJSON Coord where+ toJSON (Coord xV yV) = object [ "x" .= xV,+ "y" .= yV ]++-- A FromJSON instance allows us to decode a value from JSON. This+-- should match the format used by the ToJSON instance.++instance FromJSON Coord where+ parseJSON (Object v) = Coord <$>+ v .: "x" <*>+ v .: "y"+ parseJSON _ = empty++main :: IO ()+main = do+ let req = decode "{\"x\":3.0,\"y\":-1.0}" :: Maybe Coord+ print req+ let reply = Coord 123.4 20+ BL.putStrLn (encode reply)
+ examples/TemplateHaskell.hs view
@@ -0,0 +1,29 @@+-- We can use Template Haskell (TH) to generate instances of the+-- FromJSON and ToJSON classes automatically. This is the fastest way+-- to add JSON support for a type.++{-# LANGUAGE TemplateHaskell #-}++{-# LANGUAGE OverloadedStrings #-}++import Data.Aeson (decode, encode)+import Data.Aeson.TH (deriveJSON)+import qualified Data.ByteString.Lazy.Char8 as BL++data Coord = Coord { x :: Double, y :: Double }+ deriving (Show)++-- This splice will derive instances of ToJSON and FromJSON for us.+--+-- The use of "id" below is a placeholder function to transform the+-- names of the type's fields. We don't want to transform them, so we+-- use the identity function.++$(deriveJSON id ''Coord)++main :: IO ()+main = do+ let req = decode "{\"x\":3.0,\"y\":-1.0}" :: Maybe Coord+ print req+ let reply = Coord 123.4 20+ BL.putStrLn (encode reply)
+ release-notes.markdown view
@@ -0,0 +1,68 @@+# 0.3 to 0.4++## Ease of use++The new [`decode`+function](http://hackage.haskell.org/packages/archive/aeson/latest/doc/html/Data-Aeson.html#v:decode)+complements the longstanding `encode` function, and makes the API+simpler.++[New examples](https://github.com/bos/aeson/tree/master/examples) make+it easier to learn to use the package.+++## Generics support++aeson's support for data-type generic programming makes it possible to+use JSON encodings of most data types without writing any boilerplate+instances.++Thanks to Bas Van Dijk, aeson now supports the two major schemes for+doing datatype-generic programming:++* the modern mechanism, [built into GHC+ itself](http://www.haskell.org/ghc/docs/latest/html/users_guide/generic-programming.html)++* the older mechanism, based on SYB (aka "scrap your boilerplate")++The modern GHC-based generic mechanism is fast and terse: in fact, its+performance is generally comparable in performance to hand-written and+TH-derived `ToJSON` and `FromJSON` instances. To see how to use GHC+generics, refer to+[`examples/Generic.hs`](https://github.com/bos/aeson/blob/master/examples/Generic.hs).++The SYB-based generics support lives in+[Data.Aeson.Generic](http://hackage.haskell.org/packages/archive/aeson/latest/doc/html/Data-Aeson-Generic.html),+and is provided mainly for users of GHC older than 7.2. SYB is far+slower (by about 10x) than the more modern generic mechanism. To see+how to use SYB generics, refer to+[`examples/GenericSYB.hs`](https://github.com/bos/aeson/blob/master/examples/GenericSYB.hs).+++## Improved performance++* We switched the intermediate representation of JSON objects from+ `Data.Map` to+ [`Data.HashMap`](http://hackage.haskell.org/package/unordered-containers),+ which has improved type conversion performance.++* Instances of `ToJSON` and `FromJSON` for tuples are between 45% and+ 70% faster than in 0.3.+++## Evaluation control++This version of aeson makes explicit the decoupling between+*identifying* an element of a JSON document and *converting* it to+Haskell. See the+[`Data.Aeson.Parser`](http://hackage.haskell.org/packages/archive/aeson/latest/doc/html/Data-Aeson-Parser.html)+documentation for details.++The normal aeson `decode` function performs identification strictly,+but defers conversion until needed. This can result in improved+performance (e.g. if the results of some conversions are never+needed), but at a cost in increased memory consumption.++The new `decode'` function performs identification and conversion+immediately. This incurs an up-front cost in CPU cycles, but reduces+reduce memory consumption.
− tests/Makefile
@@ -1,9 +0,0 @@-ghc := ghc--all: qc--qc: Properties.hs- $(ghc) -Wall -o -threaded --make $@ $<--clean:- -rm -f qc *.o *.hi
tests/Properties.hs view
@@ -1,37 +1,48 @@-{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings, RecordWildCards,+ ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} +import Control.Monad+import Control.Applicative import Data.Aeson.Encode import Data.Aeson.Parser (value) import Data.Aeson.Types import Data.Attoparsec.Number+import Data.Data (Typeable, Data)+import Data.Text (Text) import Test.Framework (Test, defaultMain, testGroup) import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.QuickCheck (Arbitrary)-import qualified Data.ByteString.Lazy.Char8 as L+import Test.QuickCheck (Arbitrary(..))+import qualified Data.Aeson.Generic as G import qualified Data.Attoparsec.Lazy as L+import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.Text as T+import qualified Data.Map as Map encodeDouble :: Double -> Double -> Bool encodeDouble num denom | isInfinite d || isNaN d = encode (Number (D d)) == "null"- | otherwise = encode (Number (D d)) == L.pack (show d)+ | otherwise = (read . L.unpack . encode . Number . D) d == d where d = num / denom encodeInteger :: Integer -> Bool encodeInteger i = encode (Number (I i)) == L.pack (show i) -roundTrip :: (FromJSON a, ToJSON a) => (a -> a -> Bool) -> a -> Bool-roundTrip eq i =+roundTrip :: (FromJSON a, ToJSON a) => (a -> a -> Bool) -> a -> a -> Bool+roundTrip eq _ i = case fmap fromJSON . L.parse value . encode . toJSON $ i of L.Done _ (Success v) -> v `eq` i _ -> False -roundTripBool :: Bool -> Bool-roundTripBool = roundTrip (==)-roundTripDouble :: Double -> Bool-roundTripDouble = roundTrip approxEq-roundTripInteger :: Integer -> Bool-roundTripInteger = roundTrip (==)+roundTripEq :: (Eq a, FromJSON a, ToJSON a) => a -> a -> Bool+roundTripEq x y = roundTrip (==) x y +genericTo :: (Data a, ToJSON a) => a -> a -> Bool+genericTo _ v = G.toJSON v == toJSON v++genericFrom :: (Eq a, Data a, ToJSON a) => a -> a -> Bool+genericFrom _ v = G.fromJSON (toJSON v) == Success v+ approxEq :: Double -> Double -> Bool approxEq a b = a == b || d < maxAbsoluteError ||@@ -45,6 +56,62 @@ Error _ -> False Success x' -> x == x' +genericToFromJSON :: (Arbitrary a, Eq a, Data a) => a -> Bool+genericToFromJSON x = case G.fromJSON . G.toJSON $ x of+ Error _ -> False+ Success x' -> x == x'+++data Foo = Foo {+ fooInt :: Int+ , fooDouble :: Double+ , fooTuple :: (String, Text, Int)+ -- This definition causes an infinite loop in genericTo and genericFrom!+ -- , fooMap :: Map.Map String Foo+ , fooMap :: Map.Map String (Text,Int)+ } deriving (Show, Typeable, Data)++instance Eq Foo where+ a == b = fooInt a == fooInt b &&+ fooDouble a `approxEq` fooDouble b &&+ fooTuple a == fooTuple b++instance ToJSON Foo where+ toJSON Foo{..} = object [ "fooInt" .= fooInt+ , "fooDouble" .= fooDouble+ , "fooTuple" .= fooTuple+ , "fooMap" .= fooMap+ ]++instance FromJSON Foo where+ parseJSON (Object v) = Foo <$>+ v .: "fooInt" <*>+ v .: "fooDouble" <*>+ v .: "fooTuple" <*>+ v .: "fooMap"+ parseJSON _ = empty++instance Arbitrary Text where+ arbitrary = T.pack <$> arbitrary++instance (Ord k, Arbitrary k, Arbitrary v) => Arbitrary (Map.Map k v) where+ arbitrary = Map.fromList <$> arbitrary++instance Arbitrary Foo where+ arbitrary = liftM4 Foo arbitrary arbitrary arbitrary arbitrary++{-+ Test for Data.Aeson.Generic handling '_' names+-}+data UFoo = UFoo {+ _UFooInt :: Int+ , uFooInt :: Int+ } deriving (Show, Eq, Data, Typeable)++instance Arbitrary UFoo where+ arbitrary = UFoo <$> arbitrary <*> arbitrary+ where _ = uFooInt+ main :: IO () main = defaultMain tests @@ -54,10 +121,26 @@ testProperty "encodeDouble" encodeDouble , testProperty "encodeInteger" encodeInteger ],+ testGroup "genericFrom" [+ testProperty "Bool" $ genericFrom True+ , testProperty "Double" $ genericFrom (1::Double)+ , testProperty "Int" $ genericFrom (1::Int)+ , testProperty "Foo" $ genericFrom (undefined::Foo)+ ],+ testGroup "genericTo" [+ testProperty "Bool" $ genericTo True+ , testProperty "Double" $ genericTo (1::Double)+ , testProperty "Int" $ genericTo (1::Int)+ , testProperty "Foo" $ genericTo (undefined::Foo)+ ], testGroup "roundTrip" [- testProperty "roundTripBool" roundTripBool- , testProperty "roundTripDouble" roundTripDouble- , testProperty "roundTripInteger" roundTripInteger+ testProperty "Bool" $ roundTripEq True+ , testProperty "Double" $ roundTrip approxEq (1::Double)+ , testProperty "Int" $ roundTripEq (1::Int)+ , testProperty "Integer" $ roundTripEq (1::Integer)+ , testProperty "String" $ roundTripEq (""::String)+ , testProperty "Text" $ roundTripEq T.empty+ , testProperty "Foo" $ roundTripEq (undefined::Foo) ], testGroup "toFromJSON" [ testProperty "Integer" (toFromJSON :: Integer -> Bool)@@ -65,5 +148,8 @@ , testProperty "Maybe Integer" (toFromJSON :: Maybe Integer -> Bool) , testProperty "Either Integer Double" (toFromJSON :: Either Integer Double -> Bool) , testProperty "Either Integer Integer" (toFromJSON :: Either Integer Integer -> Bool)+ ],+ testGroup "genericToFromJSON" [+ testProperty "_UFoo" (genericToFromJSON :: UFoo -> Bool) ] ]