on eb60a38 help sign in
13 files changed+117−61
since splitsnapshots
unifiedsplit
Data/Aeson.hs+3−3 Data/Aeson/Encoding/Builder.hs−1 Data/Aeson/Encoding/Internal.hs−1 Data/Aeson/Parser/Internal.hs+29−5 Data/Aeson/TH.hs+41−14 Data/Aeson/Text.hs−1 Data/Aeson/Types.hs+1 Data/Aeson/Types/FromJSON.hs−1 Data/Aeson/Types/Internal.hs+30−14 Data/Aeson/Types/ToJSON.hs−1 aeson.cabal+2−2 attoparsec-iso8601/Data/Attoparsec/Time/Internal.hs+3−18 tests/UnitTests.hs+8
Data/Aeson.hs+3−3
@@ -149,7 +149,7 @@ import Data.Aeson.Types.FromJSON (ifromJSON, parseIndexedJSON)
149149import Data.Aeson.Encoding (encodingToLazyByteString)
150150import Data.Aeson.Parser.Internal (decodeWith, decodeStrictWith, eitherDecodeWith, eitherDecodeStrictWith, jsonEOF, json, jsonEOF', json')
151151import Data.Aeson.Types
152−import Data.Aeson.Types.Internal (JSONPath, formatError, (<?>))
152+import Data.Aeson.Types.Internal (formatError)
153153import qualified Data.ByteString as B
154154import qualified Data.ByteString.Lazy as L
155155
@@ -257,7 +257,7 @@ eitherDecodeStrict =
257257-- | Like 'decodeFileStrict' but returns an error message when decoding fails.
258258eitherDecodeFileStrict :: (FromJSON a) => FilePath -> IO (Either String a)
259259eitherDecodeFileStrict =
260− fmap (eitherFormatError . eitherDecodeStrictWith jsonEOF ifromJSON) . B.readFile
260+ fmap eitherDecodeStrict . B.readFile
261261{-# INLINE eitherDecodeFileStrict #-}
262262
263263-- | Like 'decode'' but returns an error message when decoding fails.
@@ -274,7 +274,7 @@ eitherDecodeStrict' =
274274-- | Like 'decodeFileStrict'' but returns an error message when decoding fails.
275275eitherDecodeFileStrict' :: (FromJSON a) => FilePath -> IO (Either String a)
276276eitherDecodeFileStrict' =
277− fmap (eitherFormatError . eitherDecodeStrictWith jsonEOF' ifromJSON) . B.readFile
277+ fmap eitherDecodeStrict' . B.readFile
278278{-# INLINE eitherDecodeFileStrict' #-}
279279
280280-- $use
Data/Aeson/Encoding/Builder.hs−1
@@ -44,7 +44,6 @@ import Data.ByteString.Builder as B
4444import Data.ByteString.Builder.Prim as BP
4545import Data.ByteString.Builder.Scientific (scientificBuilder)
4646import Data.Char (chr, ord)
47−import Data.Semigroup ((<>))
4847import Data.Scientific (Scientific, base10Exponent, coefficient)
4948import Data.Text.Encoding (encodeUtf8BuilderEscaped)
5049import Data.Time (UTCTime(..))
Data/Aeson/Encoding/Internal.hs−1
@@ -63,7 +63,6 @@ import Data.Aeson.Types.Internal (Value)
6363import Data.ByteString.Builder (Builder, char7, toLazyByteString)
6464import Data.Int
6565import Data.Scientific (Scientific)
66−import Data.Semigroup (Semigroup ((<>)))
6766import Data.Text (Text)
6867import Data.Time (Day, LocalTime, TimeOfDay, UTCTime, ZonedTime)
6968import Data.Typeable (Typeable)
Data/Aeson/Parser/Internal.hs+29−5
@@ -67,6 +67,9 @@ import qualified Data.Attoparsec.Lazy as L
6767import qualified Data.ByteString as B
6868import qualified Data.ByteString.Unsafe as B
6969import qualified Data.ByteString.Lazy as L
70+import qualified Data.ByteString.Lazy as BSL
71+import qualified Data.ByteString.Lazy.Char8 as C
72+import qualified Data.ByteString.Builder as B
7073import qualified Data.HashMap.Strict as H
7174import qualified Data.Scientific as Sci
7275import Data.Aeson.Parser.Unescape (unescapeText)
@@ -390,14 +393,35 @@ eitherDecodeWith p to s =
390393 L.Done _ v -> case to v of
391394 ISuccess a -> Right a
392395 IError path msg -> Left (path, msg)
393− L.Fail _ ctx msg -> Left ([], buildMsg ctx msg)
396+ L.Fail notparsed ctx msg -> Left ([], buildMsg notparsed ctx msg)
394397 where
395− buildMsg :: [String] -> String -> String
396− buildMsg [] msg = msg
397− buildMsg (expectation:_) msg =
398− msg ++ ". Expecting " ++ expectation
398+ buildMsg :: L.ByteString -> [String] -> String -> String
399+ buildMsg notYetParsed [] msg = msg ++ formatErrorLine notYetParsed
400+ buildMsg notYetParsed (expectation:_) msg =
401+ msg ++ ". Expecting " ++ expectation ++ formatErrorLine notYetParsed
399402{-# INLINE eitherDecodeWith #-}
400403
404+-- | Grab the first 100 bytes from the non parsed portion and
405+-- format to get nicer error messages
406+formatErrorLine :: L.ByteString -> String
407+formatErrorLine bs =
408+ C.unpack .
409+ -- if formatting results in empty ByteString just return that
410+ -- otherwise construct the error message with the bytestring builder
411+ (\bs' ->
412+ if BSL.null bs'
413+ then BSL.empty
414+ else
415+ B.toLazyByteString $
416+ B.stringUtf8 " at '" <> B.lazyByteString bs' <> B.stringUtf8 "'"
417+ ) .
418+ -- if newline is present cut at that position
419+ BSL.takeWhile (10 /=) .
420+ -- remove spaces, CR's, tabs, backslashes and quotes characters
421+ BSL.filter (`notElem` [9, 13, 32, 34, 47, 92]) .
422+ -- take 100 bytes
423+ BSL.take 100 $ bs
424+
401425eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
402426 -> Either (JSONPath, String) a
403427eitherDecodeStrictWith p to s =
Data/Aeson/TH.hs+41−14
@@ -116,7 +116,10 @@ module Data.Aeson.TH
116116 , mkLiftParseJSON2
117117 ) where
118118
119−import Prelude.Compat
119+import Prelude.Compat hiding (fail)
120+
121+-- We don't have MonadFail Q, so we should use `fail` from real `Prelude`
122+import Prelude (fail)
120123
121124import Control.Applicative ((<|>))
122125import Data.Aeson (Object, (.:), FromJSON(..), FromJSON1(..), FromJSON2(..), ToJSON(..), ToJSON1(..), ToJSON2(..))
@@ -1304,9 +1307,8 @@ dispatchFunByType jc jf conName tvMap list ty = do
13041307 tyVarNames :: [Name]
13051308 tyVarNames = M.keys tvMap
13061309
1307− itf <- isTyFamily tyCon
1308− if any (`mentionsName` tyVarNames) lhsArgs
1309− || itf && any (`mentionsName` tyVarNames) tyArgs
1310+ itf <- isInTypeFamilyApp tyVarNames tyCon tyArgs
1311+ if any (`mentionsName` tyVarNames) lhsArgs || itf
13101312 then outOfPlaceTyVarError jc conName
13111313 else if any (`mentionsName` tyVarNames) rhsArgs
13121314 then appsE $ varE (jsonFunValOrListName list jf $ toEnum numLastArgs)
@@ -1657,19 +1659,44 @@ isTyVar (VarT _) = True
16571659isTyVar (SigT t _) = isTyVar t
16581660isTyVar _ = False
16591661
1660−-- | Is the given type a type family constructor (and not a data family constructor)?
1661−isTyFamily :: Type -> Q Bool
1662−isTyFamily (ConT n) = do
1663− info <- reify n
1664− return $ case info of
1662+-- | Detect if a Name in a list of provided Names occurs as an argument to some
1663+-- type family. This makes an effort to exclude /oversaturated/ arguments to
1664+-- type families. For instance, if one declared the following type family:
1665+--
1666+-- @
1667+-- type family F a :: Type -> Type
1668+-- @
1669+--
1670+-- Then in the type @F a b@, we would consider @a@ to be an argument to @F@,
1671+-- but not @b@.
1672+isInTypeFamilyApp :: [Name] -> Type -> [Type] -> Q Bool
1673+isInTypeFamilyApp names tyFun tyArgs =
1674+ case tyFun of
1675+ ConT tcName -> go tcName
1676+ _ -> return False
1677+ where
1678+ go :: Name -> Q Bool
1679+ go tcName = do
1680+ info <- reify tcName
1681+ case info of
16651682#if MIN_VERSION_template_haskell(2,11,0)
1666− FamilyI OpenTypeFamilyD{} _ -> True
1683+ FamilyI (OpenTypeFamilyD (TypeFamilyHead _ bndrs _ _)) _
1684+ -> withinFirstArgs bndrs
1685+ FamilyI (ClosedTypeFamilyD (TypeFamilyHead _ bndrs _ _) _) _
1686+ -> withinFirstArgs bndrs
16671687#else
1668− FamilyI (FamilyD TypeFam _ _ _) _ -> True
1688+ FamilyI (FamilyD TypeFam _ bndrs _) _
1689+ -> withinFirstArgs bndrs
1690+ FamilyI (ClosedTypeFamilyD _ bndrs _ _) _
1691+ -> withinFirstArgs bndrs
16691692#endif
1670− FamilyI ClosedTypeFamilyD{} _ -> True
1671− _ -> False
1672−isTyFamily _ = return False
1693+ _ -> return False
1694+ where
1695+ withinFirstArgs :: [a] -> Q Bool
1696+ withinFirstArgs bndrs =
1697+ let firstArgs = take (length bndrs) tyArgs
1698+ argFVs = freeVariables firstArgs
1699+ in return $ any (`elem` argFVs) names
16731700
16741701-- | Peel off a kind signature from a Type (if it has one).
16751702unSigT :: Type -> Type
Data/Aeson/Text.hs−1
@@ -26,7 +26,6 @@ import Prelude.Compat
2626
2727import Data.Aeson.Types (Value(..), ToJSON(..))
2828import Data.Aeson.Encoding (encodingToLazyByteString)
29−import Data.Semigroup ((<>))
3029import Data.Scientific (FPFormat(..), Scientific, base10Exponent)
3130import Data.Text.Lazy.Builder
3231import Data.Text.Lazy.Builder.Scientific (formatScientificBuilder)
Data/Aeson/Types.hs+1
@@ -35,6 +35,7 @@ module Data.Aeson.Types
3535 , parse
3636 , parseEither
3737 , parseMaybe
38+ , parseFail
3839 , ToJSON(..)
3940 , KeyValue(..)
4041 , modifyFailure
Data/Aeson/Types/FromJSON.hs−1
@@ -96,7 +96,6 @@ import Data.Hashable (Hashable(..))
9696import Data.Int (Int16, Int32, Int64, Int8)
9797import Data.List.NonEmpty (NonEmpty(..))
9898import Data.Maybe (fromMaybe)
99−import Data.Semigroup ((<>))
10099import Data.Proxy (Proxy(..))
101100import Data.Ratio ((%), Ratio)
102101import Data.Scientific (Scientific, base10Exponent)
Data/Aeson/Types/Internal.hs+30−14
@@ -44,6 +44,7 @@ module Data.Aeson.Types.Internal
4444 , parse
4545 , parseEither
4646 , parseMaybe
47+ , parseFail
4748 , modifyFailure
4849 , prependFailure
4950 , parserThrowError
@@ -94,7 +95,6 @@ import Data.HashMap.Strict (HashMap)
9495import Data.Hashable (Hashable(..))
9596import Data.List (intercalate)
9697import Data.Scientific (Scientific)
97−import Data.Semigroup (Semigroup((<>)))
9898import Data.String (IsString(..))
9999import Data.Text (Text, pack, unpack)
100100import Data.Time (UTCTime)
@@ -342,6 +342,10 @@ instance Monoid (Parser a) where
342342 mappend = (<>)
343343 {-# INLINE mappend #-}
344344
345+-- | Raise a parsing failure with some custom message.
346+parseFail :: String -> Parser a
347+parseFail = fail
348+
345349apP :: Parser (a -> b) -> Parser a -> Parser b
346350apP d e = do
347351 b <- d
@@ -581,41 +585,53 @@ data Options = Options
581585 -- the constructor tag. If 'False' the encoding will always
582586 -- follow the `sumEncoding`.
583587 , omitNothingFields :: Bool
584− -- ^ If 'True' record fields with a 'Nothing' value will be
585− -- omitted from the resulting object. If 'False' the resulting
588+ -- ^ If 'True', record fields with a 'Nothing' value will be
589+ -- omitted from the resulting object. If 'False', the resulting
586590 -- object will include those fields mapping to @null@.
587591 --
592+ -- Note that this /does not/ affect parsing: 'Maybe' fields are
593+ -- optional regardless of the value of 'omitNothingFields', subject
594+ -- to the note below.
595+ --
588596 -- === Note
589597 --
590598 -- Setting 'omitNothingFields' to 'True' only affects fields which are of
591− -- type 'Maybe' /uniformly/ in the 'ToJSON' or 'FromJSON' instance. In
592− -- particular, if the type of a field is declared as a type variable, it
599+ -- type 'Maybe' /uniformly/ in the 'ToJSON' instance.
600+ -- In particular, if the type of a field is declared as a type variable, it
593601 -- will not be omitted from the JSON object, unless the field is
594602 -- specialized upfront in the instance.
595603 --
604+ -- The same holds for 'Maybe' fields being optional in the 'FromJSON' instance.
605+ --
596606 -- ==== __Example__
597607 --
598608 -- The generic instance for the following type @Fruit@ depends on whether
599609 -- the instance head is @Fruit a@ or @Fruit (Maybe a)@.
600610 --
601611 -- @
602− -- data Fruit a =
612+ -- data Fruit a = Fruit
603613 -- { apples :: a -- A field whose type is a type variable.
604614 -- , oranges :: 'Maybe' Int
605− -- }
606− --
607− -- options :: 'Options'
608− -- options = 'defaultOptions' { 'omitNothingFields' = 'True' }
615+ -- } deriving 'Generic'
609616 --
610617 -- -- apples required, oranges optional
611618 -- -- Even if 'Data.Aeson.fromJSON' is then specialized to (Fruit ('Maybe' a)).
612− -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a) where
613− -- 'Data.Aeson.fromJSON' = 'Data.Aeson.genericFromJSON' options
619+ -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a)
614620 --
615621 -- -- apples optional, oranges optional
616622 -- -- In this instance, the field apples is uniformly of type ('Maybe' a).
617− -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit ('Maybe' a)) where
618− -- 'Data.Aeson.fromJSON' = 'Data.Aeson.genericFromJSON' options
623+ -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit ('Maybe' a))
624+ --
625+ -- options :: 'Options'
626+ -- options = 'defaultOptions' { 'omitNothingFields' = 'True' }
627+ --
628+ -- -- apples always present in the output, oranges is omitted if 'Nothing'
629+ -- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit a) where
630+ -- 'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options
631+ --
632+ -- -- both apples and oranges are omitted if 'Nothing'
633+ -- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit ('Maybe' a)) where
634+ -- 'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options
619635 -- @
620636 , sumEncoding :: SumEncoding
621637 -- ^ Specifies how to encode constructors of a sum datatype.
Data/Aeson/Types/ToJSON.hs−1
@@ -79,7 +79,6 @@ import Data.Functor.Sum (Sum(..))
7979import Data.Int (Int16, Int32, Int64, Int8)
8080import Data.List (intersperse)
8181import Data.List.NonEmpty (NonEmpty(..))
82−import Data.Semigroup ((<>))
8382import Data.Proxy (Proxy(..))
8483import Data.Ratio (Ratio, denominator, numerator)
8584import Data.Scientific (Scientific)
aeson.cabal+2−2
@@ -116,8 +116,8 @@ library
116116
117117 -- Compat
118118 build-depends:
119− base-compat >= 0.9.1 && < 0.12,
120− time-compat >= 1.9.2.2 && < 1.10
119+ base-compat-batteries >= 0.10.0 && < 0.12,
120+ time-compat >= 1.9.2.2 && < 1.10
121121
122122 if !impl(ghc >= 8.6)
123123 build-depends:
attoparsec-iso8601/Data/Attoparsec/Time/Internal.hs+3−18
@@ -1,4 +1,3 @@
1−{-# LANGUAGE CPP #-}
21{-# LANGUAGE NoImplicitPrelude #-}
32-- |
43-- Module: Data.Aeson.Internal.Time
@@ -19,24 +18,10 @@ module Data.Attoparsec.Time.Internal
1918
2019import Prelude.Compat
2120
21+import Data.Fixed (Fixed(MkFixed), Pico)
2222import Data.Int (Int64)
23−import Data.Time
24−import Unsafe.Coerce (unsafeCoerce)
25−
26−#if MIN_VERSION_time(1,6,0)
27−
28−import Data.Time.Clock (diffTimeToPicoseconds)
29−
30−#endif
31−
32−import Data.Fixed (Pico, Fixed(MkFixed))
33−
34−#if !MIN_VERSION_time(1,6,0)
35−
36−diffTimeToPicoseconds :: DiffTime -> Integer
37−diffTimeToPicoseconds = unsafeCoerce
38−
39−#endif
23+import Data.Time (TimeOfDay(..))
24+import Data.Time.Clock.Compat
4025
4126toPico :: Integer -> Pico
4227toPico = MkFixed
tests/UnitTests.hs+8
@@ -5,6 +5,7 @@
55{-# LANGUAGE OverloadedStrings #-}
66{-# LANGUAGE ScopedTypeVariables #-}
77{-# LANGUAGE TemplateHaskell #-}
8+{-# LANGUAGE TypeFamilies #-}
89{-# LANGUAGE QuasiQuotes #-}
910
1011-- For Data.Aeson.Types.camelTo
@@ -747,6 +748,11 @@ bigNaturalKeyDecoding =
747748 (Left "Error in $['1e2000']: found a number with exponent 2000, but it must not be greater than 1024")
748749 ((eitherDecode :: L.ByteString -> Either String (HashMap Natural Value)) "{ \"1e2000\": null }")
749750
751+-- A regression test for: https://github.com/bos/aeson/issues/757
752+type family Fam757 :: * -> *
753+type instance Fam757 = Maybe
754+newtype Newtype757 a = MkNewtype757 (Fam757 a)
755+
750756deriveJSON defaultOptions{omitNothingFields=True} ''MyRecord
751757
752758deriveToJSON defaultOptions ''Foo
@@ -758,3 +764,5 @@ deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFields
758764deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFieldsTag
759765deriveJSON defaultOptions{tagSingleConstructors=True,rejectUnknownFields=True} ''UnknownFieldsUnaryTagged
760766deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFieldsSum
767+
768+deriveToJSON1 defaultOptions ''Newtype757