packages feed

aeson 1.4.3.0 → 1.4.4.0

raw patch · 26 files changed

+712/−147 lines, 26 filesdep +time-compatdep −time-locale-compatdep ~QuickCheckdep ~bytestringdep ~hashablePVP ok

version bump matches the API change (PVP)

Dependencies added: time-compat

Dependencies removed: time-locale-compat

Dependency ranges changed: QuickCheck, bytestring, hashable, primitive, quickcheck-instances, semigroups, template-haskell, time

API changes (from Hackage documentation)

+ Data.Aeson: class (ConstructorNames f, SumFromString f) => GFromJSONKey f
+ Data.Aeson: class GetConName f => GToJSONKey f
+ Data.Aeson: data JSONKeyOptions
+ Data.Aeson: defaultJSONKeyOptions :: JSONKeyOptions
+ Data.Aeson: genericFromJSONKey :: forall a. (Generic a, GFromJSONKey (Rep a)) => JSONKeyOptions -> FromJSONKeyFunction a
+ Data.Aeson: genericToJSONKey :: (Generic a, GToJSONKey (Rep a)) => JSONKeyOptions -> ToJSONKeyFunction a
+ Data.Aeson: keyModifier :: JSONKeyOptions -> String -> String
+ Data.Aeson.Parser: jsonAccum :: Parser Value
+ Data.Aeson.Parser: jsonAccum' :: Parser Value
+ Data.Aeson.Parser: jsonLast :: Parser Value
+ Data.Aeson.Parser: jsonLast' :: Parser Value
+ Data.Aeson.Parser: jsonNoDup :: Parser Value
+ Data.Aeson.Parser: jsonNoDup' :: Parser Value
+ Data.Aeson.Parser: jsonWith :: ([(Text, Value)] -> Either String Object) -> Parser Value
+ Data.Aeson.Parser: jsonWith' :: ([(Text, Value)] -> Either String Object) -> Parser Value
+ Data.Aeson.Parser.Internal: fromListAccum :: [(Text, Value)] -> Object
+ Data.Aeson.Parser.Internal: jsonAccum :: Parser Value
+ Data.Aeson.Parser.Internal: jsonAccum' :: Parser Value
+ Data.Aeson.Parser.Internal: jsonLast :: Parser Value
+ Data.Aeson.Parser.Internal: jsonLast' :: Parser Value
+ Data.Aeson.Parser.Internal: jsonNoDup :: Parser Value
+ Data.Aeson.Parser.Internal: jsonNoDup' :: Parser Value
+ Data.Aeson.Parser.Internal: jsonWith :: ([(Text, Value)] -> Either String Object) -> Parser Value
+ Data.Aeson.Parser.Internal: jsonWith' :: ([(Text, Value)] -> Either String Object) -> Parser Value
+ Data.Aeson.Parser.Internal: parseListNoDup :: [(Text, Value)] -> Either String Object
+ Data.Aeson.Types: class (ConstructorNames f, SumFromString f) => GFromJSONKey f
+ Data.Aeson.Types: class GetConName f => GToJSONKey f
+ Data.Aeson.Types: data JSONKeyOptions
+ Data.Aeson.Types: defaultJSONKeyOptions :: JSONKeyOptions
+ Data.Aeson.Types: genericFromJSONKey :: forall a. (Generic a, GFromJSONKey (Rep a)) => JSONKeyOptions -> FromJSONKeyFunction a
+ Data.Aeson.Types: genericToJSONKey :: (Generic a, GToJSONKey (Rep a)) => JSONKeyOptions -> ToJSONKeyFunction a
+ Data.Aeson.Types: keyModifier :: JSONKeyOptions -> String -> String

Files

Data/Aeson.hs view
@@ -70,6 +70,11 @@     , ToJSONKeyFunction(..)     , FromJSONKey(..)     , FromJSONKeyFunction(..)+    -- *** Generic keys+    , GToJSONKey()+    , genericToJSONKey+    , GFromJSONKey()+    , genericFromJSONKey     -- ** Liftings to unary and binary type constructors     , FromJSON1(..)     , parseJSON1@@ -111,6 +116,10 @@     , SumEncoding(..)     , camelTo2     , defaultTaggedObject+    -- ** Options for object keys+    , JSONKeyOptions+    , keyModifier+    , defaultJSONKeyOptions      -- * Inspecting @'Value's@     , withObject
Data/Aeson/Parser.hs view
@@ -35,10 +35,20 @@     , value     , jstring     , scientific+    -- ** Handling objects with duplicate keys+    , jsonWith+    , jsonLast+    , jsonAccum+    , jsonNoDup     -- * Strict parsers     -- $strict     , json'     , value'+    -- ** Handling objects with duplicate keys+    , jsonWith'+    , jsonLast'+    , jsonAccum'+    , jsonNoDup'     -- * Decoding without FromJSON instances     , decodeWith     , decodeStrictWith@@ -47,7 +57,7 @@     ) where  -import Data.Aeson.Parser.Internal (decodeStrictWith, decodeWith, eitherDecodeStrictWith, eitherDecodeWith, json, json', jstring, scientific, value, value')+import Data.Aeson.Parser.Internal  -- $lazy --
Data/Aeson/Parser/Internal.hs view
@@ -5,6 +5,10 @@ #if MIN_VERSION_ghc_prim(0,3,1) {-# LANGUAGE MagicHash #-} #endif+#if __GLASGOW_HASKELL__ <= 710 && __GLASGOW_HASKELL__ >= 706+-- Work around a compiler bug+{-# OPTIONS_GHC -fsimpl-tick-factor=200 #-}+#endif -- | -- Module:      Data.Aeson.Parser.Internal -- Copyright:   (c) 2011-2016 Bryan O'Sullivan@@ -21,29 +25,43 @@     (     -- * Lazy parsers       json, jsonEOF+    , jsonWith+    , jsonLast+    , jsonAccum+    , jsonNoDup     , value     , jstring     , jstring_     , scientific     -- * Strict parsers     , json', jsonEOF'+    , jsonWith'+    , jsonLast'+    , jsonAccum'+    , jsonNoDup'     , value'     -- * Helpers     , decodeWith     , decodeStrictWith     , eitherDecodeWith     , eitherDecodeStrictWith+    -- ** Handling objects with duplicate keys+    , fromListAccum+    , parseListNoDup     ) where  import Prelude.Compat  import Control.Applicative ((<|>)) import Control.Monad (void, when)-import Data.Aeson.Types.Internal (IResult(..), JSONPath, Result(..), Value(..))+import Data.Aeson.Types.Internal (IResult(..), JSONPath, Object, Result(..), Value(..)) import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)+import Data.Function (fix)+import Data.Functor.Compat (($>)) import Data.Scientific (Scientific) import Data.Text (Text)-import Data.Vector as Vector (Vector, empty, fromListN, reverse)+import Data.Vector (Vector)+import qualified Data.Vector as Vector (empty, fromList, fromListN, reverse) import qualified Data.Attoparsec.ByteString as A import qualified Data.Attoparsec.Lazy as L import qualified Data.ByteString as B@@ -75,7 +93,7 @@ #define C_n 110 #define C_t 116 --- | Parse a top-level JSON value.+-- | Parse any JSON value. -- -- The conversion of a parsed value to a Haskell value is deferred -- until the Haskell value is needed.  This may improve performance if@@ -85,10 +103,15 @@ -- This function is an alias for 'value'. In aeson 0.8 and earlier, it -- parsed only object or array types, in conformance with the -- now-obsolete RFC 4627.+--+-- ==== Warning+--+-- If an object contains duplicate keys, only the first one will be kept.+-- For a more flexible alternative, see 'jsonWith'. json :: Parser Value json = value --- | Parse a top-level JSON value.+-- | Parse any JSON value. -- -- This is a strict version of 'json' which avoids building up thunks -- during parsing; it performs all conversions immediately.  Prefer@@ -97,23 +120,35 @@ -- This function is an alias for 'value''. In aeson 0.8 and earlier, it -- parsed only object or array types, in conformance with the -- now-obsolete RFC 4627.+--+-- ==== Warning+--+-- If an object contains duplicate keys, only the first one will be kept.+-- For a more flexible alternative, see 'jsonWith''. json' :: Parser Value json' = value' -object_ :: Parser Value-object_ = {-# SCC "object_" #-} Object <$> objectValues jstring value+-- Open recursion: object_, object_', array_, array_' are parameterized by the+-- toplevel Value parser to be called recursively, to keep the parameter+-- mkObject outside of the recursive loop for proper inlining. -object_' :: Parser Value-object_' = {-# SCC "object_'" #-} do-  !vals <- objectValues jstring' value'+object_ :: ([(Text, Value)] -> Either String Object) -> Parser Value -> Parser Value+object_ mkObject val = {-# SCC "object_" #-} Object <$> objectValues mkObject jstring val+{-# INLINE object_ #-}++object_' :: ([(Text, Value)] -> Either String Object) -> Parser Value -> Parser Value+object_' mkObject val' = {-# SCC "object_'" #-} do+  !vals <- objectValues mkObject jstring' val'   return (Object vals)  where   jstring' = do     !s <- jstring     return s+{-# INLINE object_' #-} -objectValues :: Parser Text -> Parser Value -> Parser (H.HashMap Text Value)-objectValues str val = do+objectValues :: ([(Text, Value)] -> Either String Object)+             -> Parser Text -> Parser Value -> Parser (H.HashMap Text Value)+objectValues mkObject str val = do   skipSpace   w <- A.peekWord8'   if w == CLOSE_CURLY@@ -129,16 +164,20 @@     let acc' = (k, v) : acc     if ch == COMMA       then skipSpace >> loop acc'-      else return (H.fromList acc')+      else case mkObject acc' of+        Left err -> fail err+        Right obj -> pure obj {-# INLINE objectValues #-} -array_ :: Parser Value-array_ = {-# SCC "array_" #-} Array <$> arrayValues value+array_ :: Parser Value -> Parser Value+array_ val = {-# SCC "array_" #-} Array <$> arrayValues val+{-# INLINE array_ #-} -array_' :: Parser Value-array_' = {-# SCC "array_'" #-} do-  !vals <- arrayValues value'+array_' :: Parser Value -> Parser Value+array_' val = {-# SCC "array_'" #-} do+  !vals <- arrayValues val   return (Array vals)+{-# INLINE array_' #-}  arrayValues :: Parser Value -> Parser (Vector Value) arrayValues val = do@@ -156,50 +195,126 @@         else return (Vector.reverse (Vector.fromListN len (v:acc))) {-# 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&#xef;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.+-- | Parse any JSON value. Synonym of 'json'. value :: Parser Value-value = do+value = jsonWith (pure . H.fromList)++-- | Parse any JSON value.+--+-- This parser is parameterized by a function to construct an 'Object'+-- from a raw list of key-value pairs, where duplicates are preserved.+-- The pairs appear in __reverse order__ from the source.+--+-- ==== __Examples__+--+-- 'json' keeps only the first occurence of each key, using 'HashMap.Lazy.fromList'.+--+-- @+-- 'json' = 'jsonWith' ('Right' '.' 'H.fromList')+-- @+--+-- 'jsonLast' keeps the last occurence of each key, using+-- @'HashMap.Lazy.fromListWith' ('const' 'id')@.+--+-- @+-- 'jsonLast' = 'jsonWith' ('Right' '.' 'HashMap.Lazy.fromListWith' ('const' 'id'))+-- @+--+-- 'jsonAccum' keeps wraps all values in arrays to keep duplicates, using+-- 'fromListAccum'.+--+-- @+-- 'jsonAccum' = 'jsonWith' ('Right' . 'fromListAccum')+-- @+--+-- 'jsonNoDup' fails if any object contains duplicate keys, using 'parseListNoDup'.+--+-- @+-- 'jsonNoDup' = 'jsonWith' 'parseListNoDup'+-- @+jsonWith :: ([(Text, Value)] -> Either String Object) -> Parser Value+jsonWith mkObject = fix $ \value_ -> do   skipSpace   w <- A.peekWord8'   case w of     DOUBLE_QUOTE  -> A.anyWord8 *> (String <$> jstring_)-    OPEN_CURLY    -> A.anyWord8 *> object_-    OPEN_SQUARE   -> A.anyWord8 *> array_-    C_f           -> string "false" *> pure (Bool False)-    C_t           -> string "true" *> pure (Bool True)-    C_n           -> string "null" *> pure Null+    OPEN_CURLY    -> A.anyWord8 *> object_ mkObject value_+    OPEN_SQUARE   -> A.anyWord8 *> array_ value_+    C_f           -> string "false" $> Bool False+    C_t           -> string "true" $> Bool True+    C_n           -> string "null" $> Null     _              | w >= 48 && w <= 57 || w == 45                   -> Number <$> scientific       | otherwise -> fail "not a valid json value"+{-# INLINE jsonWith #-} --- | Strict version of 'value'. See also 'json''.+-- | Variant of 'json' which keeps only the last occurence of every key.+jsonLast :: Parser Value+jsonLast = jsonWith (Right . H.fromListWith (const id))++-- | Variant of 'json' wrapping all object mappings in 'Array' to preserve+-- key-value pairs with the same keys.+jsonAccum :: Parser Value+jsonAccum = jsonWith (Right . fromListAccum)++-- | Variant of 'json' which fails if any object contains duplicate keys.+jsonNoDup :: Parser Value+jsonNoDup = jsonWith parseListNoDup++-- | @'fromListAccum' kvs@ is an object mapping keys to arrays containing all+-- associated values from the original list @kvs@.+--+-- >>> fromListAccum [("apple", Bool True), ("apple", Bool False), ("orange", Bool False)]+-- fromList [("apple", [Bool False, Bool True]), ("orange", [Bool False])]+fromListAccum :: [(Text, Value)] -> Object+fromListAccum =+  fmap (Array . Vector.fromList . ($ [])) . H.fromListWith (.) . (fmap . fmap) (:)++-- | @'fromListNoDup' kvs@ fails if @kvs@ contains duplicate keys.+parseListNoDup :: [(Text, Value)] -> Either String Object+parseListNoDup =+  H.traverseWithKey unwrap . H.fromListWith (\_ _ -> Nothing) . (fmap . fmap) Just+  where+    unwrap k Nothing = Left $ "found duplicate key: " ++ show k+    unwrap _ (Just v) = Right v++-- | Strict version of 'value'. Synonym of 'json''. value' :: Parser Value-value' = do+value' = jsonWith' (pure . H.fromList)++-- | Strict version of 'jsonWith'.+jsonWith' :: ([(Text, Value)] -> Either String Object) -> Parser Value+jsonWith' mkObject = fix $ \value_ -> do   skipSpace   w <- A.peekWord8'   case w of     DOUBLE_QUOTE  -> do                      !s <- A.anyWord8 *> jstring_                      return (String s)-    OPEN_CURLY    -> A.anyWord8 *> object_'-    OPEN_SQUARE   -> A.anyWord8 *> array_'-    C_f           -> string "false" *> pure (Bool False)-    C_t           -> string "true" *> pure (Bool True)-    C_n           -> string "null" *> pure Null+    OPEN_CURLY    -> A.anyWord8 *> object_' mkObject value_+    OPEN_SQUARE   -> A.anyWord8 *> array_' value_+    C_f           -> string "false" $> Bool False+    C_t           -> string "true" $> Bool True+    C_n           -> string "null" $> Null     _              | w >= 48 && w <= 57 || w == 45                   -> do                      !n <- scientific                      return (Number n)       | otherwise -> fail "not a valid json value"+{-# INLINE jsonWith' #-}++-- | Variant of 'json'' which keeps only the last occurence of every key.+jsonLast' :: Parser Value+jsonLast' = jsonWith' (pure . H.fromListWith (const id))++-- | Variant of 'json'' wrapping all object mappings in 'Array' to preserve+-- key-value pairs with the same keys.+jsonAccum' :: Parser Value+jsonAccum' = jsonWith' (pure . fromListAccum)++-- | Variant of 'json'' which fails if any object contains duplicate keys.+jsonNoDup' :: Parser Value+jsonNoDup' = jsonWith' parseListNoDup  -- | Parse a quoted JSON string. jstring :: Parser Text
Data/Aeson/TH.hs view
@@ -159,8 +159,6 @@ import qualified Data.Vector as V (unsafeIndex, null, length, create, empty) import qualified Data.Vector.Mutable as VM (unsafeNew, unsafeWrite) -{-# ANN module "Hlint: ignore Reduce duplication" #-}- -------------------------------------------------------------------------------- -- Convenience --------------------------------------------------------------------------------
Data/Aeson/Types.hs view
@@ -53,6 +53,12 @@     , coerceFromJSONKeyFunction     , mapFromJSONKeyFunction +    -- *** Generic keys+    , GToJSONKey()+    , genericToJSONKey+    , GFromJSONKey()+    , genericFromJSONKey+     -- ** Liftings to unary and binary type constructors     , FromJSON1(..)     , parseJSON1@@ -125,6 +131,11 @@     , camelTo2     , defaultOptions     , defaultTaggedObject++    -- ** Options for object keys+    , JSONKeyOptions+    , keyModifier+    , defaultJSONKeyOptions     ) where  import Prelude.Compat
Data/Aeson/Types/Class.hs view
@@ -6,7 +6,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeSynonymInstances #-}  -- | -- Module:      Data.Aeson.Types.Class@@ -59,6 +58,11 @@     , fromJSONKeyCoerce     , coerceFromJSONKeyFunction     , mapFromJSONKeyFunction+    -- ** Generic keys+    , GToJSONKey()+    , genericToJSONKey+    , GFromJSONKey()+    , genericFromJSONKey     -- * Object key-value pairs     , KeyValue(..) 
Data/Aeson/Types/FromJSON.hs view
@@ -11,7 +11,6 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE ViewPatterns #-} @@ -46,6 +45,9 @@     , coerceFromJSONKeyFunction     , mapFromJSONKeyFunction +    , GFromJSONKey()+    , genericFromJSONKey+     -- * List functions     , listParser @@ -88,7 +90,7 @@ import Data.Aeson.Types.Generic import Data.Aeson.Types.Internal import Data.Bits (unsafeShiftR)-import Data.Fixed (Fixed, HasResolution)+import Data.Fixed (Fixed, HasResolution (resolution), Nano) import Data.Functor.Compose (Compose(..)) import Data.Functor.Identity (Identity(..)) import Data.Functor.Product (Product(..))@@ -104,8 +106,10 @@ import Data.Tagged (Tagged(..)) import Data.Text (Text, pack, unpack) import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)-import Data.Time.Format (parseTime)-import Data.Time.Locale.Compat (defaultTimeLocale)+import Data.Time.Calendar.Compat (CalendarDiffDays (..), DayOfWeek (..))+import Data.Time.LocalTime.Compat (CalendarDiffTime (..))+import Data.Time.Clock.System.Compat (SystemTime (..))+import Data.Time.Format.Compat (parseTimeM, defaultTimeLocale) import Data.Traversable as Tr (sequence) import Data.Vector (Vector) import Data.Version (Version, parseVersion)@@ -148,7 +152,9 @@ import qualified Data.Primitive.Types as PM  #if MIN_VERSION_primitive(0,6,4)+#if !MIN_VERSION_primitive(0,7,0) import qualified Data.Primitive.UnliftedArray as PM+#endif import qualified Data.Primitive.PrimArray as PM #endif @@ -403,6 +409,16 @@ --   > newtype SomeId = SomeId { getSomeId :: Text } --   >   deriving (Eq,Ord,Hashable,FromJSONKey) --+--   If you have a sum of nullary constructors, you may use the generic+--   implementation:+--+-- @+-- data Color = Red | Green | Blue+--   deriving Generic+--+-- instance 'FromJSONKey' Color where+--   'fromJSONKey' = 'genericFromJSONKey' 'defaultJSONKeyOptions'+-- @ class FromJSONKey a where     -- | Strategy for parsing the key of a map-like container.     fromJSONKey :: FromJSONKeyFunction a@@ -501,6 +517,35 @@ mapFromJSONKeyFunction :: (a -> b) -> FromJSONKeyFunction a -> FromJSONKeyFunction b mapFromJSONKeyFunction = fmap +-- | 'fromJSONKey' for 'Generic' types.+-- These types must be sums of nullary constructors, whose names will be used+-- as keys for JSON objects.+--+-- See also 'genericToJSONKey'.+--+-- === __Example__+--+-- @+-- data Color = Red | Green | Blue+--   deriving 'Generic'+--+-- instance 'FromJSONKey' Color where+--   'fromJSONKey' = 'genericFromJSONKey' 'defaultJSONKeyOptions'+-- @+genericFromJSONKey :: forall a. (Generic a, GFromJSONKey (Rep a))+             => JSONKeyOptions+             -> FromJSONKeyFunction a+genericFromJSONKey opts = FromJSONKeyTextParser $ \t ->+    case parseSumFromString (keyModifier opts) t of+        Nothing -> fail $+            "invalid key " ++ show t ++ ", expected one of " ++ show cnames+        Just k -> pure (to k)+  where+    cnames = unTagged2 (constructorTags (keyModifier opts) :: Tagged2 (Rep a) [String])++class    (ConstructorNames f, SumFromString f) => GFromJSONKey f where+instance (ConstructorNames f, SumFromString f) => GFromJSONKey f where+ ------------------------------------------------------------------------------- -- Functions needed for documentation -------------------------------------------------------------------------------@@ -1017,41 +1062,46 @@ parseAllNullarySum tname opts =     withText tname $ \tag ->         maybe (badTag tag) return $-            parseSumFromString opts tag+            parseSumFromString modifier tag   where-    badTag tag = failWithCTags tname opts $ \cnames ->+    badTag tag = failWithCTags tname modifier $ \cnames ->         "expected one of the tags " ++ show cnames ++         ", but found tag " ++ show tag+    modifier = constructorTagModifier opts  -- | Fail with an informative error message about a mismatched tag. -- The error message is parameterized by the list of expected tags, -- to be inferred from the result type of the parser. failWithCTags-  :: forall f a. ConstructorNames f-  => TypeName -> Options -> ([String] -> String) -> Parser (f a)-failWithCTags tname opts f =+  :: forall f a t. ConstructorNames f+  => TypeName -> (String -> t) -> ([t] -> String) -> Parser (f a)+failWithCTags tname modifier f =     contextType tname . fail $ f cnames   where-    cnames = unTagged2 (constructorTags opts :: Tagged2 f [String])+    cnames = unTagged2 (constructorTags modifier :: Tagged2 f [t])  class SumFromString f where-    parseSumFromString :: Options -> Text -> Maybe (f a)+    parseSumFromString :: (String -> String) -> Text -> Maybe (f a)  instance (SumFromString a, SumFromString b) => SumFromString (a :+: b) where     parseSumFromString opts key = (L1 <$> parseSumFromString opts key) <|>                                   (R1 <$> parseSumFromString opts key)  instance (Constructor c) => SumFromString (C1 c U1) where-    parseSumFromString opts key | key == name = Just $ M1 U1-                                | otherwise   = Nothing-        where-          name = pack $ constructorTagModifier opts $-                          conName (undefined :: M1 _i c _f _p)+    parseSumFromString modifier key+        | key == name = Just $ M1 U1+        | otherwise   = Nothing+      where+        name = pack $ modifier $ conName (undefined :: M1 _i c _f _p) +-- For genericFromJSONKey+instance SumFromString a => SumFromString (D1 d a) where+    parseSumFromString modifier key = M1 <$> parseSumFromString modifier key+ -- | List of all constructor tags.-constructorTags :: ConstructorNames a => Options -> Tagged2 a [String]-constructorTags opts =-    fmap DList.toList (constructorNames' (constructorTagModifier opts))+constructorTags :: ConstructorNames a => (String -> t) -> Tagged2 a [t]+constructorTags modifier =+    fmap DList.toList (constructorNames' modifier)  -- | List of all constructor names of an ADT, after a given conversion -- function. (Better inlining.)@@ -1072,6 +1122,13 @@       where         cname = conName (undefined :: M1 _i c _f _p) +-- For genericFromJSONKey+instance ConstructorNames a => ConstructorNames (D1 d a) where+    constructorNames' = retag . constructorNames'+      where+        retag :: Tagged2 a u -> Tagged2 (D1 d a) u+        retag (Tagged2 x) = Tagged2 x+ --------------------------------------------------------------------------------  parseNonAllNullarySum :: ( FromPair          arity f@@ -1123,7 +1180,7 @@        UntaggedValue -> parseUntaggedValue p   where-    failWith_ = failWithCTags tname opts+    failWith_ = failWithCTags tname (constructorTagModifier opts)  -------------------------------------------------------------------------------- @@ -1660,7 +1717,7 @@     liftParseJSON p _ = withArray "NonEmpty" $         (>>= ne) . Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList       where-        ne []     = fail "parsing NonEmpty failed, unpexpected empty list"+        ne []     = fail "parsing NonEmpty failed, unexpected empty list"         ne (x:xs) = pure (x :| xs)     {-# INLINE liftParseJSON #-} @@ -1915,7 +1972,7 @@     parseJSON = withText "DotNetTime" $ \t ->         let (s,m) = T.splitAt (T.length t - 5) t             t'    = T.concat [s,".",m]-        in case parseTime defaultTimeLocale "/Date(%s%Q)/" (unpack t') of+        in case parseTimeM True defaultTimeLocale "/Date(%s%Q)/" (unpack t') of              Just d -> pure (DotNetTime d)              _      -> fail "could not parse .NET time"     {-# INLINE parseJSON #-}@@ -1937,10 +1994,12 @@ instance (PM.Prim a,FromJSON a) => FromJSON (PM.PrimArray a) where   parseJSON = fmap Exts.fromList . parseJSON +#if !MIN_VERSION_primitive(0,7,0) instance (PM.PrimUnlifted a,FromJSON a) => FromJSON (PM.UnliftedArray a) where   parseJSON = fmap Exts.fromList . parseJSON #endif #endif+#endif  ------------------------------------------------------------------------------- -- time@@ -2008,6 +2067,40 @@ instance FromJSON DiffTime where     parseJSON = withBoundedScientific "DiffTime" $ pure . realToFrac     {-# INLINE parseJSON #-}++instance FromJSON SystemTime where+    parseJSON v = prependContext "SystemTime" $ do+        n <- parseJSON v+        let n' = floor (n * fromInteger (resolution n) :: Nano)+        let (secs, nano) = n' `divMod` resolution n+        return (MkSystemTime (fromInteger secs) (fromInteger nano))++instance FromJSON CalendarDiffTime where+    parseJSON = withObject "CalendarDiffTime" $ \obj -> CalendarDiffTime+        <$> obj .: "months"+        <*> obj .: "time"++instance FromJSON CalendarDiffDays where+    parseJSON = withObject "CalendarDiffDays" $ \obj -> CalendarDiffDays+        <$> obj .: "months"+        <*> obj .: "days"++instance FromJSON DayOfWeek where+    parseJSON = withText "DaysOfWeek" parseDayOfWeek++parseDayOfWeek :: T.Text -> Parser DayOfWeek+parseDayOfWeek t = case T.toLower t of+    "monday"    -> return Monday+    "tuesday"   -> return Tuesday+    "wednesday" -> return Wednesday+    "thursday"  -> return Thursday+    "friday"    -> return Friday+    "saturday"  -> return Saturday+    "sunday"    -> return Sunday+    _           -> fail "Invalid week day"++instance FromJSONKey DayOfWeek where+    fromJSONKey = FromJSONKeyTextParser parseDayOfWeek  ------------------------------------------------------------------------------- -- base Monoid/Semigroup
Data/Aeson/Types/Generic.hs view
@@ -5,7 +5,6 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE KindSignatures #-}-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators #-}
Data/Aeson/Types/Internal.hs view
@@ -65,8 +65,10 @@         )      , SumEncoding(..)+    , JSONKeyOptions(keyModifier)     , defaultOptions     , defaultTaggedObject+    , defaultJSONKeyOptions      -- * Used for changing CamelCase names into something else.     , camelTo@@ -158,8 +160,10 @@     IError path err >>= _ = IError path err     {-# INLINE (>>=) #-} +#if !(MIN_VERSION_base(4,13,0))     fail = Fail.fail     {-# INLINE fail #-}+#endif  instance Fail.MonadFail IResult where     fail err = IError [] err@@ -173,8 +177,10 @@     Error err >>= _ = Error err     {-# INLINE (>>=) #-} +#if !(MIN_VERSION_base(4,13,0))     fail = Fail.fail     {-# INLINE fail #-}+#endif  instance Fail.MonadFail Result where     fail err = Error err@@ -288,8 +294,11 @@     {-# INLINE (>>=) #-}     return = pure     {-# INLINE return #-}++#if !(MIN_VERSION_base(4,13,0))     fail = Fail.fail     {-# INLINE fail #-}+#endif  instance Fail.MonadFail Parser where     fail msg = Parser $ \path kf _ks -> kf (reverse path) msg@@ -561,6 +570,39 @@       -- ^ If 'True' record fields with a 'Nothing' value will be       -- omitted from the resulting object. If 'False' the resulting       -- object will include those fields mapping to @null@.+      --+      -- === Note+      --+      -- Setting 'omitNothingFields' to 'True' only affects fields which are of+      -- type 'Maybe' /uniformly/ in the 'ToJSON' or 'FromJSON' instance. In+      -- particular, if the type of a field is declared as a type variable, it+      -- will not be omitted from the JSON object, unless the field is+      -- specialized upfront in the instance.+      --+      -- ==== __Example__+      --+      -- The generic instance for the following type @Fruit@ depends on whether+      -- the instance head is @Fruit a@ or @Fruit (Maybe a)@.+      --+      -- @+      -- data Fruit a =+      --   { apples :: a  -- A field whose type is a type variable.+      --   , oranges :: 'Maybe' Int+      --   }+      --+      -- options :: 'Options'+      -- options = 'defaultOptions' { 'omitNothingFields' = 'True' }+      --+      -- -- apples required, oranges optional+      -- -- Even if 'Data.Aeson.fromJSON' is then specialized to (Fruit ('Maybe' a)).+      -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a) where+      --   'Data.Aeson.fromJSON' = 'Data.Aeson.genericFromJSON' options+      --+      -- -- apples optional, oranges optional+      -- -- In this instance, the field apples is uniformly of type ('Maybe' a).+      -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit ('Maybe' a)) where+      --   'Data.Aeson.fromJSON' = 'Data.Aeson.genericFromJSON' options+      -- @     , sumEncoding :: SumEncoding       -- ^ Specifies how to encode constructors of a sum datatype.     , unwrapUnaryRecords :: Bool@@ -625,6 +667,33 @@     -- contents of the constructor.     deriving (Eq, Show) +-- | Options for encoding keys with 'Data.Aeson.Types.genericFromJSONKey' and+-- 'Data.Aeson.Types.genericToJSONKey'.+data JSONKeyOptions = JSONKeyOptions+    { keyModifier :: String -> String+      -- ^ Function applied to keys. Its result is what goes into the encoded+      -- 'Value'.+      --+      -- === __Example__+      --+      -- The following instances encode the constructor @Bar@ to lower-case keys+      -- @\"bar\"@.+      --+      -- @+      -- data Foo = Bar+      --   deriving 'Generic'+      --+      -- opts :: 'JSONKeyOptions'+      -- opts = 'defaultJSONKeyOptions' { 'keyModifier' = 'toLower' }+      --+      -- instance 'ToJSONKey' Foo where+      --   'toJSONKey' = 'genericToJSONKey' opts+      --+      -- instance 'FromJSONKey' Foo where+      --   'fromJSONKey' = 'genericFromJSONKey' opts+      -- @+    }+ -- | Default encoding 'Options': -- -- @@@ -662,6 +731,16 @@                       { tagFieldName      = "tag"                       , contentsFieldName = "contents"                       }++-- | Default 'JSONKeyOptions':+--+-- @+-- defaultJSONKeyOptions = 'JSONKeyOptions'+--                         { 'keyModifier' = 'id'+--                         }+-- @+defaultJSONKeyOptions :: JSONKeyOptions+defaultJSONKeyOptions = JSONKeyOptions id  -- | Converts from CamelCase to another lower case, interspersing --   the character between all capital letters and their previous
Data/Aeson/Types/ToJSON.hs view
@@ -5,7 +5,6 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GADTs #-}-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-}@@ -13,7 +12,6 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE UndecidableInstances #-}  #if __GLASGOW_HASKELL__ >= 706@@ -49,6 +47,10 @@     , ToJSONKeyFunction(..)     , toJSONKeyText     , contramapToJSONKeyFunction++    , GToJSONKey()+    , genericToJSONKey+     -- * Object key-value pairs     , KeyValue(..)     , KeyValuePair(..)@@ -71,7 +73,7 @@ import Data.Attoparsec.Number (Number(..)) import Data.Bits (unsafeShiftR) import Data.DList (DList)-import Data.Fixed (Fixed, HasResolution)+import Data.Fixed (Fixed, HasResolution, Nano) import Data.Foldable (toList) import Data.Functor.Compose (Compose(..)) import Data.Functor.Contravariant (Contravariant (..))@@ -88,8 +90,10 @@ import Data.Tagged (Tagged(..)) import Data.Text (Text, pack) import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)-import Data.Time.Format (FormatTime, formatTime)-import Data.Time.Locale.Compat (defaultTimeLocale)+import Data.Time.Calendar.Compat (CalendarDiffDays (..), DayOfWeek (..))+import Data.Time.LocalTime.Compat (CalendarDiffTime (..))+import Data.Time.Clock.System.Compat (SystemTime (..))+import Data.Time.Format.Compat (FormatTime, formatTime, defaultTimeLocale) import Data.Vector (Vector) import Data.Version (Version, showVersion) import Data.Void (Void, absurd)@@ -135,7 +139,9 @@ import qualified Data.Primitive.Types as PM  #if MIN_VERSION_primitive(0,6,4)+#if !MIN_VERSION_primitive(0,7,0) import qualified Data.Primitive.UnliftedArray as PM+#endif import qualified Data.Primitive.PrimArray as PM #endif @@ -147,8 +153,6 @@ import qualified Data.ByteString.Lazy.Internal as L #endif -{-# ANN module ("HLint: ignore Reduce duplication"::String) #-}- toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b) {-# INLINE toJSONPair #-}@@ -411,7 +415,7 @@ --   newtype wrapper around 'Text'. The recommended approach is to use --   generalized newtype deriving: -----   > newtype RecordId = RecordId { getRecordId :: Text}+--   > newtype RecordId = RecordId { getRecordId :: Text } --   >   deriving (Eq,Ord,ToJSONKey) -- --   Then we may write:@@ -426,8 +430,18 @@ -- --   It is possible to get the 'ToJSONKey' instance for free as we did --   with 'Foo'. However, in this case, we have a natural way to go to---   and from 'Text' that does not require any escape sequences. So, in---   this example, 'ToJSONKeyText' will be used instead of 'ToJSONKeyValue'.+--   and from 'Text' that does not require any escape sequences. So+--   'ToJSONKeyText' can be used instead of 'ToJSONKeyValue' to encode maps+--   as objects instead of arrays of pairs. This instance may be+--   implemented using generics as follows:+--+-- @+-- instance 'ToJSONKey' Color where+--   'toJSONKey' = 'genericToJSONKey' 'defaultJSONKeyOptions'+-- @+--+--   === __Low-level implementations__+-- --   The 'Show' instance can be used to help write 'ToJSONKey': -- --   > instance ToJSONKey Color where@@ -515,6 +529,29 @@     ToJSONKeyText  f g -> ToJSONKeyText (f . h) (g . h)     ToJSONKeyValue f g -> ToJSONKeyValue (f . h) (g . h) +-- 'toJSONKey' for 'Generic' types.+-- Deriving is supported for enumeration types, i.e. the sums of nullary+-- constructors. The names of constructors will be used as keys for JSON+-- objects.+--+-- See also 'genericFromJSONKey'.+--+-- === __Example__+--+-- @+-- data Color = Red | Green | Blue+--   deriving 'Generic'+--+-- instance 'ToJSONKey' Color where+--   'toJSONKey' = 'genericToJSONKey' 'defaultJSONKeyOptions'+-- @+genericToJSONKey :: (Generic a, GToJSONKey (Rep a))+           => JSONKeyOptions -> ToJSONKeyFunction a+genericToJSONKey opts = toJSONKeyText (pack . keyModifier opts . getConName . from)++class    GetConName f => GToJSONKey f+instance GetConName f => GToJSONKey f+ ------------------------------------------------------------------------------- -- Lifings of FromJSON and ToJSON to unary and binary type constructors -------------------------------------------------------------------------------@@ -944,6 +981,9 @@ instance (Constructor c) => GetConName (C1 c a) where     getConName = conName +-- For genericToJSONKey+instance GetConName a => GetConName (D1 d a) where+    getConName (M1 x) = getConName x  -------------------------------------------------------------------------------- @@ -1959,11 +1999,13 @@   toJSON = toJSON . Exts.toList   toEncoding = toEncoding . Exts.toList +#if !MIN_VERSION_primitive(0,7,0) instance (PM.PrimUnlifted a,ToJSON a) => ToJSON (PM.UnliftedArray a) where   toJSON = toJSON . Exts.toList   toEncoding = toEncoding . Exts.toList #endif #endif+#endif  ------------------------------------------------------------------------------- -- time@@ -2032,6 +2074,50 @@      toEncoding = E.scientific . realToFrac     {-# INLINE toEncoding #-}++-- | Encoded as number+instance ToJSON SystemTime where+    toJSON (MkSystemTime secs nsecs) =+        toJSON (fromIntegral secs + fromIntegral nsecs / 1000000000 :: Nano)+    toEncoding (MkSystemTime secs nsecs) =+        toEncoding (fromIntegral secs + fromIntegral nsecs / 1000000000 :: Nano)++instance ToJSON CalendarDiffTime where+    toJSON (CalendarDiffTime m nt) = object+        [ "months" .= m+        , "time" .= nt+        ]+    toEncoding (CalendarDiffTime m nt) = E.pairs+        ("months" .= m <> "time" .= nt)++instance ToJSON CalendarDiffDays where+    toJSON (CalendarDiffDays m d) = object+        [ "months" .= m+        , "days" .= d+        ]+    toEncoding (CalendarDiffDays m d) = E.pairs+        ("months" .= m <> "days" .= d)++instance ToJSON DayOfWeek where+    toJSON Monday    = "monday"+    toJSON Tuesday   = "tuesday"+    toJSON Wednesday = "wednesday"+    toJSON Thursday  = "thursday"+    toJSON Friday    = "friday"+    toJSON Saturday  = "saturday"+    toJSON Sunday    = "sunday"++toEncodingDayOfWeek :: DayOfWeek -> E.Encoding' Text+toEncodingDayOfWeek Monday    = E.unsafeToEncoding "\"monday\""+toEncodingDayOfWeek Tuesday   = E.unsafeToEncoding "\"tuesday\""+toEncodingDayOfWeek Wednesday = E.unsafeToEncoding "\"wednesday\""+toEncodingDayOfWeek Thursday  = E.unsafeToEncoding "\"thursday\""+toEncodingDayOfWeek Friday    = E.unsafeToEncoding "\"friday\""+toEncodingDayOfWeek Saturday  = E.unsafeToEncoding "\"saturday\""+toEncodingDayOfWeek Sunday    = E.unsafeToEncoding "\"sunday\""++instance ToJSONKey DayOfWeek where+    toJSONKey = toJSONKeyTextEnc toEncodingDayOfWeek  ------------------------------------------------------------------------------- -- base Monoid/Semigroup
aeson.cabal view
@@ -1,5 +1,5 @@ name:            aeson-version:         1.4.3.0+version:         1.4.4.0 license:         BSD3 license-file:    LICENSE category:        Text, Web, JSON@@ -111,12 +111,16 @@     text             >= 1.2.3.0 && < 1.3,     time             >= 1.4     && < 1.9 +  if impl(ghc >= 8.0)+    build-depends: bytestring >= 0.10.8.1+   -- Compat   build-depends:-    base-compat     >= 0.9.1    && < 0.11+    base-compat     >= 0.9.1    && < 0.11,+    time-compat     >= 1.9.2.2  && < 1.10    if flag(bytestring-builder)-    build-depends: bytestring >= 0.9.2 && < 0.10.4,+    build-depends: bytestring >= 0.9.2.1 && < 0.10.4,                    bytestring-builder >= 0.10.4 && < 1   else     build-depends: bytestring >= 0.10.4 && < 0.11@@ -128,7 +132,7 @@   if !impl(ghc >= 8.0)     -- `Data.Semigroup` and `Control.Monad.Fail` and `Control.Monad.IO.Class` are available in base only since GHC 8.0 / base 4.9     build-depends:-      semigroups          >= 0.18.5  && < 0.19,+      semigroups          >= 0.18.5  && < 0.20,       transformers        >= 0.3.0.0 && < 0.6,       transformers-compat >= 0.6.2   && < 0.7,       fail == 4.9.*@@ -144,16 +148,15 @@      -- not in LTS-12.10     tagged               >= 0.8.5    && < 0.9,-    primitive            >= 0.6.3.0  && < 0.7+    primitive            >= 0.6.3.0  && < 0.8    -- Other dependencies   build-depends:     attoparsec           >= 0.13.2.2 && < 0.14,     dlist                >= 0.8.0.4  && < 0.9,-    hashable             >= 1.2.7.0  && < 1.3,+    hashable             >= 1.2.7.0  && < 1.4,     scientific           >= 0.3.6.2  && < 0.4,     th-abstraction       >= 0.2.8.0  && < 0.4,-    time-locale-compat   >= 0.1.1.5  && < 0.2,     uuid-types           >= 1.0.3    && < 1.1,     vector               >= 0.12.0.1 && < 0.13 @@ -211,7 +214,7 @@     UnitTests.NullaryConstructors    build-depends:-    QuickCheck >= 2.10.0.1 && < 2.13,+    QuickCheck >= 2.10.0.1 && < 2.14,     aeson,     integer-logarithms >= 1 && <1.1,     attoparsec,@@ -236,11 +239,11 @@     tasty-quickcheck,     text,     time,-    time-locale-compat,+    time-compat,     unordered-containers,     uuid-types,     vector,-    quickcheck-instances >= 0.3.16+    quickcheck-instances >= 0.3.21 && <0.4    if flag(bytestring-builder)     build-depends: bytestring >= 0.9 && < 0.10.4,@@ -250,7 +253,7 @@    if !impl(ghc >= 8.0)     build-depends:-      semigroups >= 0.18.2 && < 0.19,+      semigroups >= 0.18.2 && < 0.20,       transformers >= 0.2.2.0,       transformers-compat >= 0.3 
benchmarks/Compare/JsonBench.hs view
@@ -68,10 +68,10 @@ instance NFData Fruit where rnf !_ = ()  instance NFData Friend where-    rnf Friend {..} = rnf fId `seq` rnf fName `seq` ()+    rnf Friend {..} = rnf fId `seq` rnf fName  instance NFData User where-    rnf User {..} = rnf uId `seq` rnf uIndex `seq` rnf uGuid `seq` rnf uIsActive `seq` rnf uBalance `seq` rnf uPicture `seq` rnf uAge `seq` rnf uEyeColor `seq` rnf uName `seq` rnf uGender `seq` rnf uCompany `seq` rnf uEmail `seq` rnf uPhone `seq` rnf uAddress `seq` rnf uAbout `seq` rnf uRegistered `seq` rnf uLatitude `seq` rnf uLongitude `seq` rnf uTags `seq` rnf uFriends `seq` rnf uGreeting `seq` rnf uFavouriteFruit `seq` ()+    rnf User {..} = rnf uId `seq` rnf uIndex `seq` rnf uGuid `seq` rnf uIsActive `seq` rnf uBalance `seq` rnf uPicture `seq` rnf uAge `seq` rnf uEyeColor `seq` rnf uName `seq` rnf uGender `seq` rnf uCompany `seq` rnf uEmail `seq` rnf uPhone `seq` rnf uAddress `seq` rnf uAbout `seq` rnf uRegistered `seq` rnf uLatitude `seq` rnf uLongitude `seq` rnf uTags `seq` rnf uFriends `seq` rnf uGreeting `seq` rnf uFavouriteFruit  eyeColorTable :: [(Text, EyeColor)] eyeColorTable = [("brown", Brown), ("green", Green), ("blue", Blue)]
benchmarks/CompareWithJSON.hs view
@@ -27,7 +27,7 @@ instance NFData J.JSValue where   rnf J.JSNull = ()   rnf (J.JSBool b) = rnf b-  rnf (J.JSRational a b) = rnf a `seq` rnf b `seq` ()+  rnf (J.JSRational a b) = rnf a `seq` rnf b   rnf (J.JSString s) = rnf (J.fromJSString s)   rnf (J.JSArray lst) = rnf lst   rnf (J.JSObject o) = rnf o
benchmarks/Issue673.hs view
@@ -18,6 +18,7 @@ import Prelude.Compat import Data.Int (Int64) import Data.Scientific (Scientific)+import Data.Semigroup ((<>)) import Data.Aeson.Parser (scientific)  import qualified Data.Attoparsec.ByteString.Lazy as AttoL
benchmarks/JsonParse.hs view
@@ -17,7 +17,7 @@ instance NFData JSValue where     rnf JSNull = ()     rnf (JSBool b) = rnf b-    rnf (JSRational b r) = rnf b `seq` rnf r `seq` ()+    rnf (JSRational b r) = rnf b `seq` rnf r     rnf (JSString s) = rnf (fromJSString s)     rnf (JSArray vs) = rnf vs     rnf (JSObject kvs) = rnf (fromJSObject kvs)
benchmarks/aeson-benchmarks.cabal view
@@ -28,44 +28,42 @@     hs-source-dirs: .. ../ffi ../pure ../attoparsec-iso8601     c-sources:  ../cbits/unescape_string.c     build-depends:-      attoparsec >= 0.13.0.1,-      base == 4.*,-      base-compat >= 0.9.1 && <0.11,-      time-locale-compat >=0.1.1 && <0.2,+      attoparsec,+      base,+      base-compat,       containers,       deepseq,-      dlist >= 0.2,-      fail == 4.9.*,-      ghc-prim >= 0.2,-      hashable >= 1.1.2.0,+      dlist,+      fail,+      ghc-prim,+      hashable,       mtl,-      primitive >= 0.6.1,-      scientific >= 0.3.4.7 && < 0.4,+      primitive,+      scientific,       syb,-      tagged >=0.8.3 && <0.9,-      template-haskell >= 2.4,-      text >= 1.2.3,-      th-abstraction >= 0.2.2 && < 0.4,+      tagged,+      template-haskell,+      text,+      th-abstraction,+      time-compat,       time,       transformers,-      unordered-containers >= 0.2.3.0,-      uuid-types >= 1.0.3 && <1.1,-      vector >= 0.7.1--    if !impl(ghc >= 7.10)-      -- `Numeric.Natural` is available in base only since GHC 7.10 / base 4.8-      build-depends: nats >= 1 && < 1.2+      unordered-containers,+      uuid-types,+      vector -    if impl(ghc >=7.8)-      cpp-options: -DHAS_COERCIBLE+    if !impl(ghc >= 8.6)+      build-depends: contravariant      if !impl(ghc >= 8.0)       -- `Data.Semigroup` is available in base only since GHC 8.0 / base 4.9-      build-depends: semigroups >= 0.18.2 && < 0.19+      build-depends: semigroups,+                     transformers-compat -    if !impl(ghc >= 8.6)-      build-depends:-       contravariant >=1.4.1    && <1.6+    if !impl(ghc >= 7.10)+      -- `Numeric.Natural` is available in base only since GHC 7.10 / base 4.8+      build-depends: nats,+                     void      include-dirs: ../include @@ -178,6 +176,9 @@      Compare.JsonBuilder    build-depends:      json-builder+ if impl(ghc < 8.0)+   build-depends:+     semigroups  executable aeson-benchmark-micro   default-language: Haskell2010@@ -224,6 +225,8 @@                    bytestring-builder >= 0.10.4 && < 1   else     build-depends: bytestring >= 0.10.4+  if impl(ghc < 8.0)+    build-depends: semigroups  executable aeson-benchmark-compare-with-json   default-language: Haskell2010@@ -339,3 +342,5 @@     scientific,     base-compat,     criterion >= 1.0+  if impl(ghc < 8.0)+    build-depends: semigroups
changelog.md view
@@ -1,5 +1,40 @@ For the latest version of this document, please see [https://github.com/bos/aeson/blob/master/changelog.md](https://github.com/bos/aeson/blob/master/changelog.md). +### 1.4.4.0++**New features**:++* Adds a parameterized parser `jsonWith` that can be used to choose how to handle duplicate keys in objects, thanks to Xia Li-Yao.++* Add generic implementations of `FromJSONKey` and `ToJSONKey`, thanks to Xia Li-Yao. Example:++```haskell+data Foo = Bar+  deriving Generic++opts :: JSONKeyOptions+opts = defaultJSONKeyOptions { keyModifier = toLower }++instance ToJSONKey Foo where+  toJSONKey = genericToJSONKey opts++instance FromJSONKey Foo where+  fromJSONKey = genericFromJSONKey opts+```++**Minor**:+* aeson now uses `time-compat` instead of `time-locale-compat`, thanks to Oleg Grenrus.+* Prepare for `MonadFail` breakages in GHC 8.8, thanks to Oleg Grenrus.+* Require `bytestring >= 0.10.8.1` for newer GHCs to avoid build failures, thanks to Oleg Grenrus.+* Support `primitive 0.7.*`, thanks to Adam Bergmark.+* Allow `semigroups 0.19.*` and `hashable 1.3.*`, thanks to Oleg Grenrus.+* Fix a typo in the error message when parsing `NonEmpty`, thanks to Colin Woodbury.+* Document surprising behavior when using `omitNothingFields` with type variables, thanks to Xia Li-Yao.++**Internal changes**:+* Code cleanup by Oleg Grenrus+* Fix dependencies of the benchmarks on older GHC's, thanks to Xia Li-Yao.+ ### 1.4.3.0 * Improve error messages for FromJSON in existing instances and GHC Generic implementation. Thanks to Xia Li-Yao & Igor Pashev. * Tweak error-reporting combinators and their documentation. Thanks to Xia Li-Yao.
stack-bench.yaml view
@@ -1,4 +1,4 @@-resolver: lts-12.10+resolver: lts-12.26 # We use aeson in the snapshot to # - avoid recompilation of criterion # - compare against it@@ -9,3 +9,10 @@ work-dir: .stack-work-bench packages: - benchmarks+extra-deps:+- base-orphans-0.8.1+- hashable-time-0.2.0.1+- QuickCheck-2.13.1+- quickcheck-instances-0.3.21+- splitmix-0.0.2+- time-compat-1.9.2.2
stack-ffi-unescape.yaml view
@@ -1,4 +1,4 @@-resolver: lts-12.10+resolver: lts-12.26 packages: - '.' flags:@@ -6,4 +6,9 @@     fast: true     cffi: true extra-deps:+- base-orphans-0.8.1 - hashable-time-0.2.0.1+- QuickCheck-2.13.1+- quickcheck-instances-0.3.21+- splitmix-0.0.2+- time-compat-1.9.2.2
stack-lts12.yaml view
@@ -1,4 +1,4 @@-resolver: lts-12.10+resolver: lts-12.26 packages: - '.' - attoparsec-iso8601@@ -7,4 +7,9 @@     fast: true     cffi: true extra-deps:+- base-orphans-0.8.1 - hashable-time-0.2.0.1+- QuickCheck-2.13.1+- quickcheck-instances-0.3.21+- splitmix-0.0.2+- time-compat-1.9.2.2
stack-nightly.yaml view
@@ -1,4 +1,4 @@-resolver: nightly-2018-09-26+resolver: nightly-2019-05-13 packages: - '.' - attoparsec-iso8601@@ -7,3 +7,7 @@     fast: true   attoparsec-iso8601:     fast: true+extra-deps:+- hlint-2.1.18+- time-compat-1.9.2.2+- quickcheck-instances-0.3.21
tests/Encoders.hs view
@@ -6,6 +6,7 @@ module Encoders (module Encoders) where  import Prelude.Compat+import Data.Text (Text)  import Data.Aeson.TH import Data.Aeson.Types@@ -96,6 +97,18 @@ gNullaryParseJSONObjectWithSingleField :: Value -> Parser Nullary gNullaryParseJSONObjectWithSingleField = genericParseJSON optsObjectWithSingleField +keyOptions :: JSONKeyOptions+keyOptions = defaultJSONKeyOptions { keyModifier = ('k' :) }++gNullaryToJSONKey :: Nullary -> Either String Text+gNullaryToJSONKey x = case genericToJSONKey keyOptions of+  ToJSONKeyText p _ -> Right (p x)+  _ -> Left "Should be a ToJSONKeyText"++gNullaryFromJSONKey :: Text -> Parser Nullary+gNullaryFromJSONKey t = case genericFromJSONKey keyOptions of+  FromJSONKeyTextParser p -> p t+  _ -> fail "Not a TextParser"  -------------------------------------------------------------------------------- -- SomeType encoders/decoders
tests/PropertyRoundTrip.hs view
@@ -14,6 +14,9 @@ import Data.Tagged (Tagged) import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime) import Data.Version (Version)+import Data.Time.Calendar.Compat (CalendarDiffDays, DayOfWeek)+import Data.Time.LocalTime.Compat (CalendarDiffTime)+import Data.Time.Clock.System.Compat (SystemTime) import Instances () import Numeric.Natural (Natural) import Test.Tasty (TestTree, testGroup)@@ -47,6 +50,10 @@     , testProperty "ZonedTime" $ roundTripEq (undefined :: ZonedTime)     , testProperty "NominalDiffTime" $ roundTripEq (undefined :: NominalDiffTime)     , testProperty "DiffTime" $ roundTripEq (undefined :: DiffTime)+    , testProperty "DayOfWeek" $ roundTripEq (undefined :: DayOfWeek)+    , testProperty "SystemTime" $ roundTripEq (undefined :: SystemTime)+    , testProperty "CalendarDiffTime" $ roundTripEq (undefined :: CalendarDiffTime)+    , testProperty "CalendarDiffDays" $ roundTripEq (undefined :: CalendarDiffDays)     , testProperty "Version" $ roundTripEq (undefined :: Version)     , testProperty "Natural" $ roundTripEq (undefined :: Natural)     , testProperty "Proxy" $ roundTripEq (undefined :: Proxy Int)
tests/SerializationFormatSpec.hs view
@@ -32,6 +32,9 @@ import Data.Scientific (Scientific) import Data.Tagged (Tagged(..)) import Data.Time (fromGregorian)+import Data.Time.Calendar.Compat (CalendarDiffDays (..), DayOfWeek (..))+import Data.Time.LocalTime.Compat (CalendarDiffTime (..))+import Data.Time.Clock.System.Compat (SystemTime (..)) import Data.Word (Word8) import GHC.Generics (Generic) import Instances ()@@ -68,6 +71,9 @@     example "Either Left" "{\"Left\":1}"  (Left 1 :: Either Int Int)   , example "Either Right" "{\"Right\":1}"  (Right 1 :: Either Int Int)   , example "Nothing"  "null"  (Nothing :: Maybe Int)+  -- Maybe serialising is lossy+  -- https://github.com/bos/aeson/issues/376+  , Example "Just Nothing" ["null"] (Just Nothing :: Maybe (Maybe Int)) Nothing   , example "Just"  "1"  (Just 1 :: Maybe Int)   , example "Proxy Int" "null"  (Proxy :: Proxy Int)   , example "Tagged Char Int" "1"  (Tagged 1 :: Tagged Char Int)@@ -82,22 +88,22 @@   , example "DList" "[1,2,3]"  (DList.fromList [1, 2, 3] :: DList.DList Int)   , example "()" "[]"  () -  , Example "HashMap Int Int"+  , ndExample "HashMap Int Int"         [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]         (HM.fromList [(0,1),(2,3)] :: HM.HashMap Int Int)-  , Example "Map Int Int"+  , ndExample "Map Int Int"         [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]         (M.fromList [(0,1),(2,3)] :: M.Map Int Int)-  , Example "Map (Tagged Int Int) Int"+  , ndExample "Map (Tagged Int Int) Int"         [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]         (M.fromList [(Tagged 0,1),(Tagged 2,3)] :: M.Map (Tagged Int Int) Int)   , example "Map [Int] Int"         "[[[0],1],[[2],3]]"         (M.fromList [([0],1),([2],3)] :: M.Map [Int] Int)-  , Example "Map [Char] Int"+  , ndExample "Map [Char] Int"         [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]         (M.fromList [("ab",1),("cd",3)] :: M.Map String Int)-  , Example "Map [I Char] Int"+  , ndExample "Map [I Char] Int"         [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]         (M.fromList [(map pure "ab",1),(map pure "cd",3)] :: M.Map [I Char] Int) @@ -193,16 +199,27 @@   , example "Semigroup.WrappedMonoid Int" "2" (Semigroup.WrapMonoid 2 :: Semigroup.WrappedMonoid Int)   , example "Semigroup.Option Just" "2" (pure 2 :: Semigroup.Option Int)   , example "Semigroup.Option Nothing" "null" (Semigroup.Option (Nothing :: Maybe Bool))++  -- time 1.9+  , example "SystemTime" "123.123456789" (MkSystemTime 123 123456789)+  , Example "SystemTime" ["124.23456789"]+    (MkSystemTime 123 1234567890)+    (MkSystemTime 124 234567890)+  , ndExample "CalendarDiffTime"+    [ "{\"months\":12,\"time\":456.789}", "{\"time\":456.789,\"months\":12}" ]+    (CalendarDiffTime 12 456.789)+  , ndExample "CalendarDiffDays"+    [ "{\"months\":12,\"days\":20}", "{\"days\":20,\"months\":12}" ]+    (CalendarDiffDays 12 20)+  , example "DayOfWeek" "\"monday\"" Monday   ]  jsonEncodingExamples :: [Example] jsonEncodingExamples =   [-  -- Maybe serialising is lossy-  -- https://github.com/bos/aeson/issues/376-    example "Just Nothing" "null" (Just Nothing :: Maybe (Maybe Int))   -- infinities cannot be recovered, null is decoded as NaN-  , example "inf :: Double" "null" (Approx $ 1/0 :: Approx Double)+    example "inf :: Double" "null" (Approx $ 1/0 :: Approx Double)+  , example "-inf :: Double" "null" (Approx $ -1/0 :: Approx Double)   ]  jsonDecodingExamples :: [Example]@@ -224,17 +241,28 @@   ]  data Example where-    Example-        :: (Eq a, Show a, ToJSON a, FromJSON a)-        => String -> [L.ByteString] -> a -> Example -- empty bytestring will fail, any p [] == False-    MaybeExample-        :: (Eq a, Show a, FromJSON a)-        => String -> L.ByteString -> Maybe a -> Example+  Example+    :: (Eq a, Show a, ToJSON a, FromJSON a)+    => String          -- name+    -> [L.ByteString]  -- encoded variants+    -> a               -- input+    -> a               -- decoded+    -> Example         -- empty bytestring will fail, any p [] == False +  MaybeExample+    :: (Eq a, Show a, FromJSON a)+    => String -> L.ByteString -> Maybe a -> Example+ example :: (Eq a, Show a, ToJSON a, FromJSON a)         => String -> L.ByteString -> a -> Example-example n bs x = Example n [bs] x+example n bs x = Example n [bs] x x +-- | Non-deterministic example, input encodes to some of bytestrings.+ndExample :: (Eq a, Show a, ToJSON a, FromJSON a)+          => String -> [L.ByteString] -> a -> Example+ndExample n bss x = Example n bss x x++ data MyEither a b = MyLeft a | MyRight b   deriving (Generic, Show, Eq) @@ -246,16 +274,16 @@     parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }  assertJsonExample :: Example -> TestTree-assertJsonExample (Example name bss val) = testCase name $ do+assertJsonExample (Example name bss val val') = testCase name $ do     assertSomeEqual "encode"           bss        (encode val)     assertSomeEqual "encode/via value" bss        (encode $ toJSON val)     for_ bss $ \bs ->-        assertEqual "decode"           (Just val) (decode bs)+        assertEqual "decode"           (Just val') (decode bs) assertJsonExample (MaybeExample name bs mval) = testCase name $     assertEqual "decode" mval (decode bs)  assertJsonEncodingExample :: Example -> TestTree-assertJsonEncodingExample (Example name bss val) = testCase name $ do+assertJsonEncodingExample (Example name bss val _) = testCase name $ do     assertSomeEqual "encode"           bss (encode val)     assertSomeEqual "encode/via value" bss (encode $ toJSON val) assertJsonEncodingExample (MaybeExample name _ _) = testCase name $
tests/UnitTests.hs view
@@ -29,7 +29,13 @@ import Data.Aeson.QQ.Simple (aesonQQ) import Data.Aeson.TH (deriveJSON, deriveToJSON, deriveToJSON1) import Data.Aeson.Text (encodeToTextBuilder)-import Data.Aeson.Types (Options(..), Result(Success), ToJSON(..), Value(Null, Object), camelTo, camelTo2, defaultOptions, omitNothingFields, parse)+import Data.Aeson.Parser+  ( json, jsonLast, jsonAccum, jsonNoDup+  , json', jsonLast', jsonAccum', jsonNoDup')+import Data.Aeson.Types+  ( Options(..), Result(Success), ToJSON(..), Value(Array, Bool, Null, Object)+  , camelTo, camelTo2, defaultOptions, omitNothingFields, parse)+import Data.Attoparsec.ByteString (Parser, parseOnly) import Data.Char (toUpper) import Data.Either.Compat (isLeft, isRight) import Data.Hashable (hash)@@ -40,24 +46,26 @@ import Data.Tagged (Tagged(..)) import Data.Text (Text) import Data.Time (UTCTime)-import Data.Time.Format (parseTime)-import Data.Time.Locale.Compat (defaultTimeLocale)+import Data.Time.Format.Compat (parseTimeM, defaultTimeLocale) import GHC.Generics (Generic) import Instances () import Numeric.Natural (Natural) import System.Directory (getDirectoryContents) import System.FilePath ((</>), takeExtension, takeFileName) import Test.Tasty (TestTree, testGroup)-import Test.Tasty.HUnit (Assertion, assertBool, assertFailure, assertEqual, testCase)+import Test.Tasty.HUnit (Assertion, assertBool, assertFailure, assertEqual, testCase, (@?=)) import Text.Printf (printf) import UnitTests.NullaryConstructors (nullaryConstructors)+import qualified Data.ByteString as S import qualified Data.ByteString.Base16.Lazy as LBase16 import qualified Data.ByteString.Lazy.Char8 as L import qualified Data.HashSet as HashSet+import qualified Data.HashMap.Lazy as HashMap import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy.Builder as TLB import qualified Data.Text.Lazy.Encoding as LT import qualified Data.Text.Lazy.Encoding as TLE+import qualified Data.Vector as Vector import qualified ErrorMessages import qualified SerializationFormatSpec @@ -103,6 +111,7 @@   , testGroup "SingleMaybeField" singleMaybeField   , testCase "withEmbeddedJSON" withEmbeddedJSONTest   , testCase "SingleFieldCon" singleFieldCon+  , testGroup "Ordering of object keys" keyOrdering   , testCase "Ratio with denominator 0" ratioDenominator0   , testCase "Big scientific exponent" bigScientificExponent   , testCase "Big integer decoding" bigIntegerDecoding@@ -215,7 +224,7 @@   where     parseWithRead :: String -> LT.Text -> UTCTime     parseWithRead f s =-      fromMaybe (error "parseTime input malformed") . parseTime defaultTimeLocale f . LT.unpack $ s+      fromMaybe (error "parseTime input malformed") . parseTimeM True defaultTimeLocale f . LT.unpack $ s     parseWithAeson :: LT.Text -> Maybe UTCTime     parseWithAeson s = decode . LT.encodeUtf8 $ LT.concat ["\"", s, "\""] @@ -557,6 +566,40 @@ singleFieldCon :: Assertion singleFieldCon =   assertEqual "fromJSON" (Right (SingleFieldCon 0)) (eitherDecode "0")++testParser :: (Eq a, Show a)+           => String -> Parser a -> S.ByteString -> Either String a -> TestTree+testParser name json_ s expected =+  testCase name (parseOnly json_ s @?= expected)++keyOrdering :: [TestTree]+keyOrdering =+  [ testParser "json" json+      "{\"k\":true,\"k\":false}" $+      Right (Object (HashMap.fromList [("k", Bool True)]))+  , testParser "jsonLast" jsonLast+      "{\"k\":true,\"k\":false}" $+      Right (Object (HashMap.fromList [("k", Bool False)]))+  , testParser "jsonAccum" jsonAccum+      "{\"k\":true,\"k\":false}" $+      Right (Object (HashMap.fromList [("k", Array (Vector.fromList [Bool True, Bool False]))]))+  , testParser "jsonNoDup" jsonNoDup+      "{\"k\":true,\"k\":false}" $+      Left "Failed reading: found duplicate key: \"k\""++  , testParser "json'" json'+      "{\"k\":true,\"k\":false}" $+      Right (Object (HashMap.fromList [("k", Bool True)]))+  , testParser "jsonLast'" jsonLast'+      "{\"k\":true,\"k\":false}" $+      Right (Object (HashMap.fromList [("k", Bool False)]))+  , testParser "jsonAccum'" jsonAccum'+      "{\"k\":true,\"k\":false}" $+      Right (Object (HashMap.fromList [("k", Array (Vector.fromList [Bool True, Bool False]))]))+  , testParser "jsonNoDup'" jsonNoDup'+      "{\"k\":true,\"k\":false}" $+      Left "Failed reading: found duplicate key: \"k\""+  ]  ratioDenominator0 :: Assertion ratioDenominator0 =
tests/UnitTests/NullaryConstructors.hs view
@@ -14,6 +14,7 @@ import Data.Aeson.Internal (IResult (..), iparse) import Data.Aeson.Types (Parser) import Data.ByteString.Builder (toLazyByteString)+import Data.Foldable (for_) import Data.Maybe (fromJust) import Encoders import Test.Tasty.HUnit ((@=?), Assertion)@@ -52,6 +53,10 @@     -- Make sure that the old `"contents" : []' is still allowed   , ISuccess C1 @=? parse thNullaryParseJSONTaggedObject          (dec "{\"tag\":\"c1\",\"contents\":[]}")   , ISuccess C1 @=? parse gNullaryParseJSONTaggedObject           (dec "{\"tag\":\"c1\",\"contents\":[]}")++  , for_ [("kC1", C1), ("kC2", C2), ("kC3", C3)] $ \(jkey, key) -> do+      Right   jkey @=? gNullaryToJSONKey key+      ISuccess key @=? parse gNullaryFromJSONKey jkey   ]   where     enc = eitherDecode . toLazyByteString . fromEncoding