packages feed

aeson 2.0.3.0 → 2.1.0.0

raw patch · 14 files changed

+374/−36 lines, 14 filesdep +genericallydep ~ghc-primdep ~template-haskellPVP ok

version bump matches the API change (PVP)

Dependencies added: generically

Dependency ranges changed: ghc-prim, template-haskell

API changes (from Hackage documentation)

+ Data.Aeson.KeyMap: data Key
+ Data.Aeson.KeyMap: mapWithKey :: (Key -> a -> b) -> KeyMap a -> KeyMap b
+ Data.Aeson.Types: IError :: JSONPath -> String -> IResult a
+ Data.Aeson.Types: ISuccess :: a -> IResult a
+ Data.Aeson.Types: data IResult a
+ Data.Aeson.Types: ifromJSON :: FromJSON a => Value -> IResult a
+ Data.Aeson.Types: iparse :: (a -> Parser b) -> a -> IResult b
+ Data.Aeson.Types: iparseEither :: (a -> Parser b) -> a -> Either (JSONPath, String) b

Files

aeson.cabal view
@@ -1,5 +1,5 @@ name:               aeson-version:            2.0.3.0+version:            2.1.0.0 license:            BSD3 license-file:       LICENSE category:           Text, Web, JSON@@ -16,8 +16,10 @@    || ==8.4.4    || ==8.6.5    || ==8.8.4-   || ==8.10.4-   || ==9.0.1+   || ==8.10.7+   || ==9.0.2+   || ==9.2.3+   || ==9.4.1  synopsis:           Fast JSON parsing and encoding cabal-version:      >=1.10@@ -105,6 +107,7 @@   -- Compat   build-depends:       base-compat-batteries  >=0.10.0 && <0.13+    , generically            >=0.1    && <0.2     , time-compat            >=1.9.6  && <1.10    if !impl(ghc >=8.6)@@ -192,6 +195,7 @@     , dlist     , filepath     , generic-deriving      >=1.10     && <1.15+    , generically     , ghc-prim              >=0.2     , hashable     , indexed-traversable
attoparsec-iso8601/src/Data/Attoparsec/Time.hs view
@@ -19,6 +19,7 @@     , timeZone     , utcTime     , zonedTime+    , year     , month     , quarter     ) where@@ -27,7 +28,7 @@  import Control.Applicative ((<|>)) import Control.Monad (void, when)-import Data.Attoparsec.Text (Parser, char, decimal, digit, option, anyChar, peekChar, peekChar', takeWhile1, satisfy)+import Data.Attoparsec.Text (Parser, char, digit, option, anyChar, peekChar, peekChar', takeWhile1, satisfy) import Data.Attoparsec.Time.Internal (toPico) import Data.Bits ((.&.)) import Data.Char (isDigit, ord)@@ -35,6 +36,7 @@ import Data.Int (Int64) import Data.Maybe (fromMaybe) import Data.Time.Calendar (Day, fromGregorianValid)+import Data.Time.Calendar.Compat (Year) import Data.Time.Calendar.Quarter.Compat (Quarter, QuarterOfYear (..), fromYearQuarter) import Data.Time.Calendar.Month.Compat (Month, fromYearMonthValid) import Data.Time.Clock (UTCTime(..))@@ -42,27 +44,37 @@ import qualified Data.Time.LocalTime as Local  -- | Parse a date of the form @[+,-]YYYY-MM-DD@.+--+-- The year must contain at least 4 digits, to avoid the Y2K problem:+-- a two-digit year @YY@ may mean @YY@, @19YY@, or @20YY@, and we make it+-- an error to prevent the ambiguity.+-- Years from @0000@ to @0999@ must thus be zero-padded.+-- The year may have more than 4 digits. day :: Parser Day day = do   absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id-  y <- (decimal <* char '-') <|> fail "date must be of form [+,-]YYYY-MM-DD"+  y <- (year <* char '-') <|> fail "date must be of form [+,-]YYYY-MM-DD"   m <- (twoDigits <* char '-') <|> fail "date must be of form [+,-]YYYY-MM-DD"   d <- twoDigits <|> fail "date must be of form [+,-]YYYY-MM-DD"   maybe (fail "invalid date") return (fromGregorianValid (absOrNeg y) m d)  -- | Parse a month of the form @[+,-]YYYY-MM@.+--+-- See also 'day' for details about the year format. month :: Parser Month month = do   absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id-  y <- (decimal <* char '-') <|> fail "month must be of form [+,-]YYYY-MM"+  y <- (year <* char '-') <|> fail "month must be of form [+,-]YYYY-MM"   m <- twoDigits <|> fail "month must be of form [+,-]YYYY-MM"   maybe (fail "invalid month") return (fromYearMonthValid (absOrNeg y) m)  -- | Parse a quarter of the form @[+,-]YYYY-QN@.+--+-- See also 'day' for details about the year format. quarter :: Parser Quarter quarter = do   absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id-  y <- (decimal <* char '-') <|> fail "month must be of form [+,-]YYYY-MM"+  y <- (year <* char '-') <|> fail "month must be of form [+,-]YYYY-MM"   _ <- char 'q' <|> char 'Q'   q <- parseQ   return $! fromYearQuarter (absOrNeg y) q@@ -72,6 +84,19 @@       <|> Q3 <$ char '3'       <|> Q4 <$ char '4' +-- | Parse a year @YYYY@, with at least 4 digits. Does not include any sign.+--+-- Note: 'Year' is a type synonym for 'Integer'.+--+-- @since 1.1.0.0+year :: Parser Year+year = do+  ds <- takeWhile1 isDigit+  if T.length ds < 4 then+    fail "expected year with at least 4 digits"+  else+    pure (txtToInteger ds)+ -- | Parse a two-digit integer (e.g. day of month, hour). twoDigits :: Parser Int twoDigits = do@@ -172,3 +197,53 @@  utc :: Local.TimeZone utc = Local.TimeZone 0 False ""++------------------ Copy-pasted and adapted from base ------------------------++txtToInteger :: T.Text -> Integer+txtToInteger bs+    | l > 40    = valInteger 10 l [ fromIntegral (ord w - 48) | w <- T.unpack bs ]+    | otherwise = txtToIntegerSimple bs+  where+    l = T.length bs++txtToIntegerSimple :: T.Text -> Integer+txtToIntegerSimple = T.foldl' step 0 where+  step a b = a * 10 + fromIntegral (ord b - 48) -- 48 = '0'++-- A sub-quadratic algorithm for Integer. Pairs of adjacent radix b+-- digits are combined into a single radix b^2 digit. This process is+-- repeated until we are left with a single digit. This algorithm+-- performs well only on large inputs, so we use the simple algorithm+-- for smaller inputs.+valInteger :: Integer -> Int -> [Integer] -> Integer+valInteger = go+  where+    go :: Integer -> Int -> [Integer] -> Integer+    go _ _ []  = 0+    go _ _ [d] = d+    go b l ds+        | l > 40 = b' `seq` go b' l' (combine b ds')+        | otherwise = valSimple b ds+      where+        -- ensure that we have an even number of digits+        -- before we call combine:+        ds' = if even l then ds else 0 : ds+        b' = b * b+        l' = (l + 1) `quot` 2++    combine b (d1 : d2 : ds) = d `seq` (d : combine b ds)+      where+        d = d1 * b + d2+    combine _ []  = []+    combine _ [_] = errorWithoutStackTrace "this should not happen"++-- The following algorithm is only linear for types whose Num operations+-- are in constant time.+valSimple :: Integer -> [Integer] -> Integer+valSimple base = go 0+  where+    go r [] = r+    go r (d : ds) = r' `seq` go r' ds+      where+        r' = r * base + fromIntegral d
changelog.md view
@@ -1,5 +1,16 @@ For the latest version of this document, please see [https://github.com/haskell/aeson/blob/master/changelog.md](https://github.com/haskell/aeson/blob/master/changelog.md). +### 2.1.0.0++- Change time instances of types with year (`Day`, `UTCTime`) to require years with at least 4 digits.+- Change `KeyValue` instances to be more general (and use equality to constraint them) instead of being more lax flexible instances.+- Export `Key` type also from `Data.Aeson.KeyMap` module.+- Export `mapWithKey` from `Data.Aeson.KeyMap` module.+- Export `ifromJSON` and `iparse` from `Data.Aeson.Types`. Add `iparseEither`.+- Add `MonadFix Parser` instance.+- Make `Semigroup Series` slightly lazier+- Add instances for `Generically` type+ ### 2.0.3.0  * `text-2.0` support
src/Data/Aeson.hs view
@@ -34,6 +34,12 @@      -- ** Direct encoding     -- $encoding++    -- * Remarks on specific encodings+    -- ** Time+    -- $time++    -- * Main encoding and decoding functions       decode     , decode'     , eitherDecode@@ -528,3 +534,36 @@ -- > > import Data.Sequence as Seq -- > > encode (Seq.fromList [1,2,3]) -- > "[1,2,3]"++-- $time+--+-- This module contains instances of 'ToJSON' and 'FromJSON' for types from+-- the <https://hackage.haskell.org/package/time time> library.+--+-- Those instances encode time as JSON strings in+-- <https://en.wikipedia.org/wiki/ISO_8601 ISO 8601> formats, with the+-- following general form for 'Data.Time.Clock.UTCTime' and+-- 'Data.Time.LocalTime.ZonedTime', while other time types use subsets of those+-- fields:+--+-- > [+,-]YYYY-MM-DDThh:mm[:ss[.sss]]Z+--+-- where+--+-- - @[+,-]@ is an optional sign, @+@ or @-@.+-- - @YYYY@ is the year, which must have at least 4 digits to prevent Y2K problems.+--   Years from @0000@ to @0999@ must thus be zero-padded.+-- - @MM@ is a two-digit month.+-- - @DD@ is a two-digit day.+-- - @T@ is a literal @\'T\'@ character separating the date and the time of+--   day. It may be a space instead.+-- - @hh@ is a two-digit hour.+-- - @mm@ is a two-digit minute.+-- - @ss@ is a two-digit second.+-- - @sss@ is a decimal fraction of a second; it may have any nonzero number of digits.+-- - @Z@ is a time zone; it may be preceded by an optional space.+--+-- For more information, see <https://en.wikipedia.org/wiki/ISO_8601 ISO 8601>+-- <https://hackage.haskell.org/package/time time>,+-- and <https://hackage.haskell.org/package/attoparsec-iso8601 attoparsec-iso8601>+-- (where the relevant parsers are defined).
src/Data/Aeson/Encoding/Internal.hs view
@@ -158,9 +158,10 @@ {-# INLINE unsafePairSBS #-}  instance Semigroup Series where-    Empty   <> a       = a-    a       <> Empty   = a-    Value a <> Value b = Value (a >< comma >< b)+    Empty   <> a = a+    Value a <> b = Value $ a >< case b of+        Empty   -> empty+        Value x -> comma >< x  instance Monoid Series where     mempty  = Empty
src/Data/Aeson/KeyMap.hs view
@@ -65,6 +65,7 @@     -- * Traversal     -- ** Map     map,+    mapWithKey,     mapKeyVal,     traverse,     traverseWithKey,@@ -85,6 +86,9 @@     filterWithKey,     mapMaybe,     mapMaybeWithKey,++    -- * Key Type+    Key, ) where  -- Import stuff from Prelude explicitly@@ -191,6 +195,8 @@ map = fmap  -- | Map a function over all values in the map.+--+-- @since 2.1.0.0 mapWithKey :: (Key -> a -> b) -> KeyMap a -> KeyMap b mapWithKey f (KeyMap m) = KeyMap (M.mapWithKey f m) @@ -245,7 +251,7 @@  -- | Return a list of this map' elements. ----- @since 2.0.2.0+-- @since 2.0.3.0 elems :: KeyMap v -> [v] elems = M.elems . unKeyMap @@ -392,6 +398,8 @@ map = fmap  -- | Map a function over all values in the map.+--+-- @since 2.1.0.0 mapWithKey :: (Key -> a -> b) -> KeyMap a -> KeyMap b mapWithKey f (KeyMap m) = KeyMap (H.mapWithKey f m) @@ -446,7 +454,7 @@  -- | Return a list of this map' elements. ----- @since 2.0.2.0+-- @since 2.0.3.0 elems :: KeyMap v -> [v] elems = H.elems . unKeyMap 
src/Data/Aeson/Parser/Internal.hs view
@@ -133,11 +133,11 @@ -- mkObject outside of the recursive loop for proper inlining.  object_ :: ([(Key, Value)] -> Either String Object) -> Parser Value -> Parser Value-object_ mkObject val = {-# SCC "object_" #-} Object <$> objectValues mkObject key val+object_ mkObject val = Object <$> objectValues mkObject key val {-# INLINE object_ #-}  object_' :: ([(Key, Value)] -> Either String Object) -> Parser Value -> Parser Value-object_' mkObject val' = {-# SCC "object_'" #-} do+object_' mkObject val' = do   !vals <- objectValues mkObject key' val'   return (Object vals)  where@@ -172,11 +172,11 @@ {-# INLINE objectValues #-}  array_ :: Parser Value -> Parser Value-array_ val = {-# SCC "array_" #-} Array <$> arrayValues val+array_ val = Array <$> arrayValues val {-# INLINE array_ #-}  array_' :: Parser Value -> Parser Value-array_' val = {-# SCC "array_'" #-} do+array_' val = do   !vals <- arrayValues val   return (Array vals) {-# INLINE array_' #-}@@ -344,7 +344,7 @@  jstringSlow :: B.ByteString -> Parser Text {-# INLINE jstringSlow #-}-jstringSlow s' = {-# SCC "jstringSlow" #-} do+jstringSlow s' = do   s <- A.scan startState go <* A.anyWord8   case unescapeText (B.append s' s) of     Right r  -> return r
src/Data/Aeson/Text.hs view
@@ -56,18 +56,18 @@ encodeToTextBuilder =     go . toJSON   where-    go Null       = {-# SCC "go/Null" #-} "null"-    go (Bool b)   = {-# SCC "go/Bool" #-} if b then "true" else "false"-    go (Number s) = {-# SCC "go/Number" #-} fromScientific s-    go (String s) = {-# SCC "go/String" #-} string s+    go Null       = "null"+    go (Bool b)   = if b then "true" else "false"+    go (Number s) = fromScientific s+    go (String s) = string s     go (Array v)-        | V.null v = {-# SCC "go/Array" #-} "[]"-        | otherwise = {-# SCC "go/Array" #-}+        | V.null v = "[]"+        | otherwise =                        TB.singleton '[' <>                       go (V.unsafeHead v) <>                       V.foldr f (TB.singleton ']') (V.unsafeTail v)       where f a z = TB.singleton ',' <> go a <> z-    go (Object m) = {-# SCC "go/Object" #-}+    go (Object m) =          case KM.toList m of           (x:xs) -> TB.singleton '{' <> one x <> foldr f (TB.singleton '}') xs           _      -> "{}"@@ -75,12 +75,12 @@             one (k,v) = string (Key.toText k) <> TB.singleton ':' <> go v  string :: T.Text -> Builder-string s = {-# SCC "string" #-} TB.singleton '"' <> quote s <> TB.singleton '"'+string s = TB.singleton '"' <> quote s <> TB.singleton '"'   where     quote q = case T.uncons t of                 Nothing      -> TB.fromText h                 Just (!c,t') -> TB.fromText h <> escape c <> quote t'-        where (h,t) = {-# SCC "break" #-} T.break isEscape q+        where (h,t) = T.break isEscape q     isEscape c = c == '\"' ||                  c == '\\' ||                  c < '\x20'
src/Data/Aeson/Types.hs view
@@ -29,6 +29,7 @@     , typeMismatch     , unexpected     -- * Type conversion+    -- ** Parsing     , Parser     , Result(..)     , FromJSON(..)@@ -37,12 +38,19 @@     , parseEither     , parseMaybe     , parseFail-    , ToJSON(..)-    , KeyValue(..)+    -- ** Parser error handling     , modifyFailure     , prependFailure     , parserThrowError     , parserCatchError+    -- ** Parsing with paths+    , IResult (..)+    , ifromJSON+    , iparse+    , iparseEither+    -- ** Encoding+    , ToJSON(..)+    , KeyValue(..)      -- ** Keys for maps     , ToJSONKey(..)
src/Data/Aeson/Types/FromJSON.hs view
@@ -118,6 +118,9 @@ import Foreign.Storable (Storable) import Foreign.C.Types (CTime (..)) import GHC.Generics+#if !MIN_VERSION_base(4,17,0)+import GHC.Generics.Generically (Generically (..), Generically1 (..))+#endif import Numeric.Natural (Natural) import Text.ParserCombinators.ReadP (readP_to_S) import Unsafe.Coerce (unsafeCoerce)@@ -362,6 +365,12 @@ -- instance 'FromJSON' Coord -- @ --+-- or using the [DerivingVia extension](https://downloads.haskell.org/ghc/9.2.3/docs/html/users_guide/exts/deriving_via.html)+--+-- @+-- deriving via 'Generically' Coord instance 'FromJSON' Coord+-- @+-- -- The default implementation will be equivalent to -- @parseJSON = 'genericParseJSON' 'defaultOptions'@; if you need different -- options, you can customize the generic decoding by defining:@@ -386,6 +395,10 @@         . V.toList         $ a +-- | @since 2.1.0.0+instance (Generic a, GFromJSON Zero (Rep a)) => FromJSON (Generically a) where+    parseJSON = coerce (genericParseJSON defaultOptions :: Value -> Parser a)+ ------------------------------------------------------------------------------- --  Classes and types for map keys -------------------------------------------------------------------------------@@ -509,7 +522,7 @@  -- | Fail parsing due to a type mismatch, with a descriptive message. ----- The following wrappers should generally be prefered:+-- The following wrappers should generally be preferred: -- 'withObject', 'withArray', 'withText', 'withBool'. -- -- ==== Error message example@@ -575,6 +588,12 @@ -- instance 'FromJSON' a => 'FromJSON1' (Pair a) -- @ --+-- or+--+-- @+-- deriving via 'Generically1' (Pair a) instance 'FromJSON1' (Pair a)+-- @+-- -- If the default implementation doesn't give exactly the results you want, -- you can customize the generic decoding with only a tiny amount of -- effort, using 'genericLiftParseJSON' with your preferred 'Options':@@ -597,6 +616,11 @@     liftParseJSONList :: (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser [f a]     liftParseJSONList f g v = listParser (liftParseJSON f g) v +-- | @since 2.1.0.0+instance (Generic1 f, GFromJSON One (Rep1 f)) => FromJSON1 (Generically1 f) where+    liftParseJSON :: forall a. (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (Generically1 f a)+    liftParseJSON = coerce (genericLiftParseJSON defaultOptions :: (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a))+ -- | Lift the standard 'parseJSON' function through the type constructor. parseJSON1 :: (FromJSON1 f, FromJSON a) => Value -> Parser (f a) parseJSON1 = liftParseJSON parseJSON parseJSONList@@ -1697,7 +1721,7 @@  -- | @since 2.0.2.0 instance FromJSON ST.ShortText where-    parseJSON = withText "Lazy Text" $ pure . ST.fromText+    parseJSON = withText "ShortText" $ pure . ST.fromText  -- | @since 2.0.2.0 instance FromJSONKey ST.ShortText where
src/Data/Aeson/Types/Internal.hs view
@@ -43,6 +43,7 @@     , JSONPathElement(..)     , JSONPath     , iparse+    , iparseEither     , parse     , parseEither     , parseMaybe@@ -89,6 +90,7 @@ import Control.Applicative (Alternative(..)) import Control.DeepSeq (NFData(..)) import Control.Monad (MonadPlus(..), ap)+import Control.Monad.Fix (MonadFix (..)) import Data.Char (isLower, isUpper, toLower, isAlpha, isAlphaNum) import Data.Aeson.Key (Key) import Data.Data (Data)@@ -306,6 +308,19 @@     {-# INLINE fail #-} #endif +-- |+--+-- @since 2.1.0.0+instance MonadFix Parser where+    mfix f = Parser $ \path kf ks -> let x = runParser (f (fromISuccess x)) path IError ISuccess in+        case x of+            IError p e -> kf p e+            ISuccess y -> ks y+      where+        fromISuccess :: IResult a -> a+        fromISuccess (ISuccess x)      = x+        fromISuccess (IError path msg) = error $ "mfix @Aeson.Parser: " ++ formatPath path ++ ": " ++ msg+ instance Fail.MonadFail Parser where     fail msg = Parser $ \path kf _ks -> kf (reverse path) msg     {-# INLINE fail #-}@@ -579,6 +594,14 @@ parseEither m v = runParser (m v) [] onError Right   where onError path msg = Left (formatError path msg) {-# INLINE parseEither #-}++-- | Run a 'Parser' with an 'Either' result type.+-- If the parse fails, the 'Left' payload will contain an error message and a json path to failed element.+--+-- @since 2.1.0.0+iparseEither :: (a -> Parser b) -> a -> Either (JSONPath, String) b+iparseEither m v = runParser (m v) [] (\path msg -> Left (path, msg)) Right+{-# INLINE iparseEither #-}  -- | Annotate an error message with a -- <http://goessner.net/articles/JsonPath/ JSONPath> error location.
src/Data/Aeson/Types/ToJSON.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-}@@ -68,6 +69,7 @@ import qualified Data.Aeson.KeyMap as KM import Data.Attoparsec.Number (Number(..)) import Data.Bits (unsafeShiftR)+import Data.Coerce (coerce) import Data.DList (DList) import Data.Fixed (Fixed, HasResolution, Nano) import Data.Foldable (toList)@@ -101,6 +103,9 @@ import Foreign.Storable (Storable) import Foreign.C.Types (CTime (..)) import GHC.Generics+#if !MIN_VERSION_base(4,17,0)+import GHC.Generics.Generically (Generically (..), Generically1 (..))+#endif import Numeric.Natural (Natural) import qualified Data.Aeson.Encoding as E import qualified Data.Aeson.Encoding.Internal as E (InArray, comma, econcat, retagEncoding, key)@@ -256,6 +261,12 @@ --     'toEncoding' = 'genericToEncoding' 'defaultOptions' -- @ --+-- or more conveniently using the [DerivingVia extension](https://downloads.haskell.org/ghc/9.2.3/docs/html/users_guide/exts/deriving_via.html)+--+-- @+-- deriving via 'Generically' Coord instance 'ToJSON' Coord+-- @+-- -- If on the other hand you wish to customize the generic decoding, you have -- to implement both methods: --@@ -272,7 +283,7 @@ -- Previous versions of this library only had the 'toJSON' method. Adding -- 'toEncoding' had two reasons: ----- 1. toEncoding is more efficient for the common case that the output of+-- 1. 'toEncoding' is more efficient for the common case that the output of -- 'toJSON' is directly serialized to a @ByteString@. -- Further, expressing either method in terms of the other would be -- non-optimal.@@ -321,6 +332,11 @@     toEncodingList :: [a] -> Encoding     toEncodingList = listEncoding toEncoding +-- | @since 2.1.0.0+instance (Generic a, GToJSON' Value Zero (Rep a), GToJSON' Encoding Zero (Rep a)) => ToJSON (Generically a) where+    toJSON     = coerce (genericToJSON     defaultOptions :: a -> Value)+    toEncoding = coerce (genericToEncoding defaultOptions :: a -> Encoding)+ ------------------------------------------------------------------------------- -- Object key-value pairs -------------------------------------------------------------------------------@@ -334,14 +350,14 @@     name .= value = E.pair name (toEncoding value)     {-# INLINE (.=) #-} -instance KeyValue Pair where+instance (key ~ Key, value ~ Value) => KeyValue (key, value) where     name .= value = (name, toJSON value)     {-# INLINE (.=) #-}  -- | Constructs a singleton 'KM.KeyMap'. For calling functions that --   demand an 'Object' for constructing objects. To be used in --   conjunction with 'mconcat'. Prefer to use 'object' where possible.-instance KeyValue Object where+instance value ~ Value => KeyValue (KM.KeyMap value) where     name .= value = KM.singleton name (toJSON value)     {-# INLINE (.=) #-} @@ -615,6 +631,14 @@     liftToEncodingList :: (a -> Encoding) -> ([a] -> Encoding) -> [f a] -> Encoding     liftToEncodingList f g = listEncoding (liftToEncoding f g) +-- | @since 2.1.0.0+instance (Generic1 f, GToJSON' Value One (Rep1 f), GToJSON' Encoding One (Rep1 f)) => ToJSON1 (Generically1 f) where+    liftToJSON :: forall a. (a -> Value) -> ([a] -> Value) -> Generically1 f a -> Value+    liftToJSON = coerce (genericLiftToJSON defaultOptions :: (a -> Value) -> ([a] -> Value) -> f a -> Value)++    liftToEncoding :: forall a. (a -> Encoding) -> ([a] -> Encoding) -> Generically1 f a -> Encoding+    liftToEncoding = coerce (genericLiftToEncoding defaultOptions :: (a -> Encoding) -> ([a] -> Encoding) -> f a -> Encoding)+ -- | Lift the standard 'toJSON' function through the type constructor. toJSON1 :: (ToJSON1 f, ToJSON a) => f a -> Value toJSON1 = liftToJSON toJSON toJSONList@@ -1979,6 +2003,8 @@     toJSON Q2 = "q2"     toJSON Q3 = "q3"     toJSON Q4 = "q4"++    toEncoding = toEncodingQuarterOfYear  toEncodingQuarterOfYear :: QuarterOfYear -> E.Encoding' a toEncodingQuarterOfYear Q1 = E.unsafeToEncoding "\"q1\""
tests/SerializationFormatSpec.hs view
@@ -31,8 +31,8 @@ import Data.Tagged (Tagged(..)) import Data.Text (Text) import Data.These (These (..))-import Data.Time (fromGregorian)-import Data.Time.Calendar.Month.Compat (fromYearMonth)+import Data.Time (Day, fromGregorian)+import Data.Time.Calendar.Month.Compat (Month, fromYearMonth) import Data.Time.Calendar.Quarter.Compat (fromYearQuarter, QuarterOfYear (..)) import Data.Time.Calendar.Compat (CalendarDiffDays (..), DayOfWeek (..)) import Data.Time.LocalTime.Compat (CalendarDiffTime (..))@@ -159,12 +159,14 @@   , example "Maybe String" "\"foo\""          (pure "foo" :: Maybe String)   , example "Maybe [Identity Char]" "\"xy\""  (pure [pure 'x', pure 'y'] :: Maybe [Identity Char]) +  , example "Day; year >= 10000" "\"10000-01-01\""      (fromGregorian 10000   1  1)   , example "Day; year >= 1000" "\"1999-10-12\""        (fromGregorian 1999    10 12)   , example "Day; year > 0 && < 1000" "\"0500-03-04\""  (fromGregorian 500     3  4)   , example "Day; year == 0" "\"0000-02-20\""           (fromGregorian 0       2  20)   , example "Day; year < 0" "\"-0234-01-01\""           (fromGregorian (-234)  1  1)   , example "Day; year < -1000" "\"-1234-01-01\""       (fromGregorian (-1234) 1  1) +  , example "Month; year >= 10000" "\"10000-01\""      (fromYearMonth 10000   1)   , example "Month; year >= 1000" "\"1999-10\""        (fromYearMonth 1999    10)   , example "Month; year > 0 && < 1000" "\"0500-03\""  (fromYearMonth 500     3)   , example "Month; year == 0" "\"0000-02\""           (fromYearMonth 0       2)@@ -297,6 +299,8 @@   , MaybeExample "Word8 300"  "300"  (Nothing :: Maybe Word8)   -- Negative zero year, encoding never produces such:   , MaybeExample "Day -0000-02-03" "\"-0000-02-03\"" (Just (fromGregorian 0 2 3))+  , MaybeExample "Day; year too short" "\"10-10-10\"" (Nothing :: Maybe Day)+  , MaybeExample "Month; year too short" "\"10-10\"" (Nothing :: Maybe Month)   ]  data Example where
tests/UnitTests.hs view
@@ -7,7 +7,13 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecursiveDo #-} +#if __GLASGOW_HASKELL__ >= 806+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE StandaloneDeriving #-}+#endif+ -- For Data.Aeson.Types.camelTo {-# OPTIONS_GHC -fno-warn-deprecations #-} @@ -37,7 +43,9 @@ import Data.Aeson.Types   ( Options(..), Result(Success, Error), ToJSON(..)   , Value(Array, Bool, Null, Number, Object, String), camelTo, camelTo2-  , defaultOptions, formatPath, formatRelativePath, omitNothingFields, parse)+  , explicitParseField, liftParseJSON, listParser+  , defaultOptions, formatPath, formatRelativePath, omitNothingFields, parse, parseMaybe)+import qualified Data.Aeson.Types import qualified Data.Aeson.KeyMap as KM import Data.Attoparsec.ByteString (Parser, parseOnly) import Data.Char (toUpper, GeneralCategory(Control,Surrogate), generalCategory)@@ -52,6 +60,7 @@ import Data.Time (UTCTime) import Data.Time.Format.Compat (parseTimeM, defaultTimeLocale) import GHC.Generics (Generic)+import GHC.Generics.Generically (Generically (..)) import Instances () import Numeric.Natural (Natural) import System.Directory (getDirectoryContents)@@ -71,6 +80,7 @@ import qualified Data.Vector as Vector import qualified ErrorMessages import qualified SerializationFormatSpec+import qualified Data.Map as Map -- Lazy!  roundTripCamel :: String -> Assertion roundTripCamel name = assertEqual "" name (camelFrom '_' $ camelTo '_' name)@@ -93,9 +103,13 @@  instance FromJSON Wibble +#if __GLASGOW_HASKELL__ >= 806+deriving via Generically Wibble instance ToJSON Wibble+#else instance ToJSON Wibble where     toJSON     = genericToJSON defaultOptions     toEncoding = genericToEncoding defaultOptions+#endif  -- Test that if we put a bomb in a data structure, but only demand -- part of it via lazy encoding, we do not unexpectedly fail.@@ -718,6 +732,106 @@ deriveToJSON1 defaultOptions ''Newtype757  -------------------------------------------------------------------------------+-- MonadFix+-------------------------------------------------------------------------------++monadFixDecoding1 :: (Value -> Data.Aeson.Types.Parser [Char]) -> Assertion+monadFixDecoding1 p = do+    fmap (take 10) (parseMaybe p value) @?= Just "xyzxyzxyzx"+  where+    value = object+        [ "foo" .= ('x', "bar" :: String)+        , "bar" .= ('y', "quu" :: String)+        , "quu" .= ('z', "foo" :: String)+        ]++monadFixDecoding2 :: (Value -> Data.Aeson.Types.Parser [Char]) -> Assertion+monadFixDecoding2 p = do+    fmap (take 10) (parseMaybe p value) @?= Nothing+  where+    value = object+        [ "foo" .= ('x', "bar" :: String)+        , "bar" .= ('y', "???" :: String)+        , "quu" .= ('z', "foo" :: String)+        ]++monadFixDecoding3 :: (Value -> Data.Aeson.Types.Parser [Char]) -> Assertion+monadFixDecoding3 p =+    fmap (take 10) (parseMaybe p value) @?= Nothing+  where+    value = object+        [ "foo" .= ('x', "bar" :: String)+        , "bar" .= Null+        , "quu" .= ('z', "foo" :: String)+        ]++monadFixDecoding4 :: (Value -> Data.Aeson.Types.Parser [Char]) -> Assertion+monadFixDecoding4 p =+    fmap (take 10) (parseMaybe p value) @?= Nothing+  where+    value = object+        [ "els" .= ('x', "bar" :: String)+        , "bar" .= Null+        , "quu" .= ('z', "foo" :: String)+        ]++-- Parser with explicit references+monadFixParserA :: Value -> Data.Aeson.Types.Parser [Char]+monadFixParserA = withObject "Rec" $ \obj -> mdo+    let p'' :: Value -> Data.Aeson.Types.Parser String+        p'' "foo" = return foo+        p'' "bar" = return bar+        p'' "quu" = return quu+        p'' _     = fail "Invalid reference"++    let p' :: Value -> Data.Aeson.Types.Parser [Char]+        p' v = do+            (c, cs) <- liftParseJSON p'' (listParser p'') v+            return (c : cs)++    foo <- explicitParseField p' obj "foo"+    bar <- explicitParseField p' obj "bar"+    quu <- explicitParseField p' obj "quu"+    return foo++-- Parser with arbitrary references!+monadFixParserB :: Value -> Data.Aeson.Types.Parser [Char]+monadFixParserB = withObject "Rec" $ \obj -> mdo+    let p'' :: Value -> Data.Aeson.Types.Parser String+        p'' key' = do+            key <- parseJSON key'+            -- this is ugly: we look whether key is in original obj+            -- but then query from refs.+            --+            -- This way we are lazier. Map.traverse isn't lazy enough.+            case KM.lookup key obj of+                Just _  -> return (refs Map.! key)+                Nothing -> fail "Invalid reference"++    let p' :: Value -> Data.Aeson.Types.Parser [Char]+        p' v = do+            (c, cs) <- liftParseJSON p'' (listParser p'') v+            return (c : cs)++    refs <- traverse p' (KM.toMap obj)+    case Map.lookup "foo" refs of+        Nothing   -> fail "No foo node"+        Just root -> return root++monadFixTests :: TestTree+monadFixTests = testGroup "MonadFix"+    [ testCase "Example1a" $ monadFixDecoding1 monadFixParserA+    , testCase "Example2a" $ monadFixDecoding2 monadFixParserA+    , testCase "Example3a" $ monadFixDecoding3 monadFixParserA+    , testCase "Example4a" $ monadFixDecoding4 monadFixParserA++    , testCase "Example1b" $ monadFixDecoding1 monadFixParserB+    , testCase "Example2b" $ monadFixDecoding2 monadFixParserB+    , testCase "Example3b" $ monadFixDecoding3 monadFixParserB+    , testCase "Example4b" $ monadFixDecoding4 monadFixParserB+    ]++------------------------------------------------------------------------------- -- Tests trees ------------------------------------------------------------------------------- @@ -779,4 +893,5 @@     [ testCase "example" $       assertEqual "" (object ["foo" .= True]) [aesonQQ| {"foo": true } |]     ]+  , monadFixTests   ]