on e41a401 help sign in
Data/Aeson.hs+2−2
since splitsnapshots
unifiedsplit
@@ -257,7 +257,7 @@ eitherDecodeStrict =
257-- | Like 'decodeFileStrict' but returns an error message when decoding fails.
257-- | Like 'decodeFileStrict' but returns an error message when decoding fails.
258eitherDecodeFileStrict :: (FromJSON a) => FilePath -> IO (Either String a)
258eitherDecodeFileStrict :: (FromJSON a) => FilePath -> IO (Either String a)
259eitherDecodeFileStrict =
259eitherDecodeFileStrict =
260− fmap (eitherFormatError . eitherDecodeStrictWith jsonEOF ifromJSON) . B.readFile
260+ fmap eitherDecodeStrict . B.readFile
261{-# INLINE eitherDecodeFileStrict #-}
261{-# INLINE eitherDecodeFileStrict #-}
262
262
263-- | Like 'decode'' but returns an error message when decoding fails.
263-- | Like 'decode'' but returns an error message when decoding fails.
@@ -274,7 +274,7 @@ eitherDecodeStrict' =
274-- | Like 'decodeFileStrict'' but returns an error message when decoding fails.
274-- | Like 'decodeFileStrict'' but returns an error message when decoding fails.
275eitherDecodeFileStrict' :: (FromJSON a) => FilePath -> IO (Either String a)
275eitherDecodeFileStrict' :: (FromJSON a) => FilePath -> IO (Either String a)
276eitherDecodeFileStrict' =
276eitherDecodeFileStrict' =
277− fmap (eitherFormatError . eitherDecodeStrictWith jsonEOF' ifromJSON) . B.readFile
277+ fmap eitherDecodeStrict' . B.readFile
278{-# INLINE eitherDecodeFileStrict' #-}
278{-# INLINE eitherDecodeFileStrict' #-}
279
279
280-- $use
280-- $use
Data/Aeson/Parser/Internal.hs+30−5
@@ -59,6 +59,7 @@ import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDi
59import Data.Function (fix)
59import Data.Function (fix)
60import Data.Functor.Compat (($>))
60import Data.Functor.Compat (($>))
61import Data.Scientific (Scientific)
61import Data.Scientific (Scientific)
62+import Data.Semigroup ((<>))
62import Data.Text (Text)
63import Data.Text (Text)
63import Data.Vector (Vector)
64import Data.Vector (Vector)
64import qualified Data.Vector as Vector (empty, fromList, fromListN, reverse)
65import qualified Data.Vector as Vector (empty, fromList, fromListN, reverse)
@@ -67,6 +68,9 @@ import qualified Data.Attoparsec.Lazy as L
67import qualified Data.ByteString as B
68import qualified Data.ByteString as B
68import qualified Data.ByteString.Unsafe as B
69import qualified Data.ByteString.Unsafe as B
69import qualified Data.ByteString.Lazy as L
70import qualified Data.ByteString.Lazy as L
71+import qualified Data.ByteString.Lazy as BSL
72+import qualified Data.ByteString.Lazy.Char8 as C
73+import qualified Data.ByteString.Builder as B
70import qualified Data.HashMap.Strict as H
74import qualified Data.HashMap.Strict as H
71import qualified Data.Scientific as Sci
75import qualified Data.Scientific as Sci
72import Data.Aeson.Parser.Unescape (unescapeText)
76import Data.Aeson.Parser.Unescape (unescapeText)
@@ -390,14 +394,35 @@ eitherDecodeWith p to s =
390 L.Done _ v -> case to v of
394 L.Done _ v -> case to v of
391 ISuccess a -> Right a
395 ISuccess a -> Right a
392 IError path msg -> Left (path, msg)
396 IError path msg -> Left (path, msg)
393− L.Fail _ ctx msg -> Left ([], buildMsg ctx msg)
397+ L.Fail notparsed ctx msg -> Left ([], buildMsg notparsed ctx msg)
394 where
398 where
395− buildMsg :: [String] -> String -> String
399+ buildMsg :: L.ByteString -> [String] -> String -> String
396− buildMsg [] msg = msg
400+ buildMsg notYetParsed [] msg = msg ++ formatErrorLine notYetParsed
397− buildMsg (expectation:_) msg =
401+ buildMsg notYetParsed (expectation:_) msg =
398− msg ++ ". Expecting " ++ expectation
402+ msg ++ ". Expecting " ++ expectation ++ formatErrorLine notYetParsed
399{-# INLINE eitherDecodeWith #-}
403{-# INLINE eitherDecodeWith #-}
400
404
405+-- | Grab the first 100 bytes from the non parsed portion and
406+-- format to get nicer error messages
407+formatErrorLine :: L.ByteString -> String
408+formatErrorLine bs =
409+ C.unpack .
410+ -- if formatting results in empty ByteString just return that
411+ -- otherwise construct the error message with the bytestring builder
412+ (\bs' ->
413+ if BSL.null bs'
414+ then BSL.empty
415+ else
416+ B.toLazyByteString $
417+ B.stringUtf8 " at '" <> B.lazyByteString bs' <> B.stringUtf8 "'"
418+ ) .
419+ -- if newline is present cut at that position
420+ BSL.takeWhile (10 /=) .
421+ -- remove spaces, CR's, tabs, backslashes and quotes characters
422+ BSL.filter (`notElem` [9, 13, 32, 34, 47, 92]) .
423+ -- take 100 bytes
424+ BSL.take 100 $ bs
425+
401eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
426eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
402 -> Either (JSONPath, String) a
427 -> Either (JSONPath, String) a
403eitherDecodeStrictWith p to s =
428eitherDecodeStrictWith p to s =
Data/Aeson/Types/Internal.hs+25−13
@@ -581,41 +581,53 @@ data Options = Options
581 -- the constructor tag. If 'False' the encoding will always
581 -- the constructor tag. If 'False' the encoding will always
582 -- follow the `sumEncoding`.
582 -- follow the `sumEncoding`.
583 , omitNothingFields :: Bool
583 , omitNothingFields :: Bool
584− -- ^ If 'True' record fields with a 'Nothing' value will be
584+ -- ^ If 'True', record fields with a 'Nothing' value will be
585− -- omitted from the resulting object. If 'False' the resulting
585+ -- omitted from the resulting object. If 'False', the resulting
586 -- object will include those fields mapping to @null@.
586 -- object will include those fields mapping to @null@.
587 --
587 --
588+ -- Note that this /does not/ affect parsing: 'Maybe' fields are
589+ -- optional regardless of the value of 'omitNothingFields', subject
590+ -- to the note below.
591+ --
588 -- === Note
592 -- === Note
589 --
593 --
590 -- Setting 'omitNothingFields' to 'True' only affects fields which are of
594 -- Setting 'omitNothingFields' to 'True' only affects fields which are of
591− -- type 'Maybe' /uniformly/ in the 'ToJSON' or 'FromJSON' instance. In
595+ -- type 'Maybe' /uniformly/ in the 'ToJSON' instance.
592− -- particular, if the type of a field is declared as a type variable, it
596+ -- In particular, if the type of a field is declared as a type variable, it
593 -- will not be omitted from the JSON object, unless the field is
597 -- will not be omitted from the JSON object, unless the field is
594 -- specialized upfront in the instance.
598 -- specialized upfront in the instance.
595 --
599 --
600+ -- The same holds for 'Maybe' fields being optional in the 'FromJSON' instance.
601+ --
596 -- ==== __Example__
602 -- ==== __Example__
597 --
603 --
598 -- The generic instance for the following type @Fruit@ depends on whether
604 -- The generic instance for the following type @Fruit@ depends on whether
599 -- the instance head is @Fruit a@ or @Fruit (Maybe a)@.
605 -- the instance head is @Fruit a@ or @Fruit (Maybe a)@.
600 --
606 --
601 -- @
607 -- @
602− -- data Fruit a =
608+ -- data Fruit a = Fruit
603 -- { apples :: a -- A field whose type is a type variable.
609 -- { apples :: a -- A field whose type is a type variable.
604 -- , oranges :: 'Maybe' Int
610 -- , oranges :: 'Maybe' Int
605− -- }
611+ -- } deriving 'Generic'
606− --
607− -- options :: 'Options'
608− -- options = 'defaultOptions' { 'omitNothingFields' = 'True' }
609 --
612 --
610 -- -- apples required, oranges optional
613 -- -- apples required, oranges optional
611 -- -- Even if 'Data.Aeson.fromJSON' is then specialized to (Fruit ('Maybe' a)).
614 -- -- Even if 'Data.Aeson.fromJSON' is then specialized to (Fruit ('Maybe' a)).
612− -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a) where
615+ -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a)
613− -- 'Data.Aeson.fromJSON' = 'Data.Aeson.genericFromJSON' options
614 --
616 --
615 -- -- apples optional, oranges optional
617 -- -- apples optional, oranges optional
616 -- -- In this instance, the field apples is uniformly of type ('Maybe' a).
618 -- -- 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
619+ -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit ('Maybe' a))
618− -- 'Data.Aeson.fromJSON' = 'Data.Aeson.genericFromJSON' options
620+ --
621+ -- options :: 'Options'
622+ -- options = 'defaultOptions' { 'omitNothingFields' = 'True' }
623+ --
624+ -- -- apples always present in the output, oranges is omitted if 'Nothing'
625+ -- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit a) where
626+ -- 'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options
627+ --
628+ -- -- both apples and oranges are omitted if 'Nothing'
629+ -- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit ('Maybe' a)) where
630+ -- 'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options
619 -- @
631 -- @
620 , sumEncoding :: SumEncoding
632 , sumEncoding :: SumEncoding
621 -- ^ Specifies how to encode constructors of a sum datatype.
633 -- ^ Specifies how to encode constructors of a sum datatype.