aeson 2.1.0.0 → 2.1.1.0
raw patch · 84 files changed
+328/−183 lines, 84 filesbinary-addedPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Data.Aeson.TH: instance Data.Aeson.TH.LookupField (Data.Semigroup.Option a)
+ Data.Aeson.KeyMap: (!?) :: KeyMap v -> Key -> Maybe v
+ Data.Aeson.KeyMap: insertWith :: (a -> a -> a) -> Key -> a -> KeyMap a -> KeyMap a
Files
- aeson.cabal +7/−5
- changelog.md +7/−0
- src/Data/Aeson/Internal/ByteString.hs +3/−2
- src/Data/Aeson/KeyMap.hs +24/−0
- src/Data/Aeson/Parser/Internal.hs +122/−73
- tests/JSONTestSuite.hs +80/−0
- tests/JSONTestSuite/test_parsing/i_number_double_huge_neg_exp.json +1/−0
- tests/JSONTestSuite/test_parsing/i_number_huge_exp.json +1/−0
- tests/JSONTestSuite/test_parsing/i_number_neg_int_huge_exp.json +1/−0
- tests/JSONTestSuite/test_parsing/i_number_pos_double_huge_exp.json +1/−0
- tests/JSONTestSuite/test_parsing/i_number_real_neg_overflow.json +1/−0
- tests/JSONTestSuite/test_parsing/i_number_real_pos_overflow.json +1/−0
- tests/JSONTestSuite/test_parsing/i_number_real_underflow.json +1/−0
- tests/JSONTestSuite/test_parsing/i_number_too_big_neg_int.json +1/−0
- tests/JSONTestSuite/test_parsing/i_number_too_big_pos_int.json +1/−0
- tests/JSONTestSuite/test_parsing/i_number_very_big_negative_int.json +1/−0
- tests/JSONTestSuite/test_parsing/i_string_UTF8_surrogate_U+D800.json +1/−0
- tests/JSONTestSuite/test_parsing/i_string_invalid_utf-8.json +1/−0
- tests/JSONTestSuite/test_parsing/i_string_iso_latin_1.json +1/−0
- tests/JSONTestSuite/test_parsing/i_string_lone_utf8_continuation_byte.json +1/−0
- tests/JSONTestSuite/test_parsing/i_string_overlong_sequence_2_bytes.json +1/−0
- tests/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes.json +1/−0
- tests/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes_null.json +1/−0
- tests/JSONTestSuite/test_parsing/i_string_unicode_U+10FFFE_nonchar.json +0/−1
- tests/JSONTestSuite/test_parsing/i_string_unicode_U+1FFFE_nonchar.json +0/−1
- tests/JSONTestSuite/test_parsing/i_string_unicode_U+FDD0_nonchar.json +0/−1
- tests/JSONTestSuite/test_parsing/i_string_unicode_U+FFFE_nonchar.json +0/−1
- tests/JSONTestSuite/test_parsing/i_string_utf16BE_no_BOM.json binary
- tests/JSONTestSuite/test_parsing/i_string_utf16LE_no_BOM.json binary
- tests/JSONTestSuite/test_parsing/n_multidigit_number_then_00.json binary
- tests/JSONTestSuite/test_parsing/n_number_then_00.json binary
- tests/JSONTestSuite/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json +1/−0
- tests/JSONTestSuite/test_parsing/n_object_pi_in_key_and_trailing_comma.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape u.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape u1.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape u1x.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u.json +1/−0
- tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1.json +1/−0
- tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1x.json +1/−0
- tests/JSONTestSuite/test_parsing/n_string_UTF8_surrogate_U+D800.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_invalid_utf-8.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_iso_latin_1.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_lone_utf8_continuation_byte.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_overlong_sequence_2_bytes.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_overlong_sequence_6_bytes.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_overlong_sequence_6_bytes_null.json +0/−1
- tests/JSONTestSuite/test_parsing/n_string_unescaped_crtl_char.json binary
- tests/JSONTestSuite/test_parsing/n_string_unescaped_ctrl_char.json binary
- tests/JSONTestSuite/test_parsing/n_structure_angle_bracket_null.json +1/−0
- tests/JSONTestSuite/test_parsing/n_structure_null.json +0/−1
- tests/JSONTestSuite/test_parsing/n_structure_single_eacute.json +1/−0
- tests/JSONTestSuite/test_parsing/n_structure_single_point.json +0/−1
- tests/JSONTestSuite/test_parsing/y_number_double_huge_neg_exp.json +0/−1
- tests/JSONTestSuite/test_parsing/y_number_huge_exp.json +0/−1
- tests/JSONTestSuite/test_parsing/y_number_neg_int_huge_exp.json +0/−1
- tests/JSONTestSuite/test_parsing/y_number_pos_double_huge_exp.json +0/−1
- tests/JSONTestSuite/test_parsing/y_number_real_neg_overflow.json +0/−1
- tests/JSONTestSuite/test_parsing/y_number_real_pos_overflow.json +0/−1
- tests/JSONTestSuite/test_parsing/y_number_real_underflow.json +0/−1
- tests/JSONTestSuite/test_parsing/y_number_too_big_neg_int.json +0/−1
- tests/JSONTestSuite/test_parsing/y_number_too_big_pos_int.json +0/−1
- tests/JSONTestSuite/test_parsing/y_number_very_big_negative_int.json +0/−1
- tests/JSONTestSuite/test_parsing/y_object_extreme_numbers.json +0/−0
- tests/JSONTestSuite/test_parsing/y_string_nbsp_uescaped.json +1/−0
- tests/JSONTestSuite/test_parsing/y_string_newline_uescaped.json +0/−1
- tests/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json +0/−1
- tests/JSONTestSuite/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json +1/−0
- tests/JSONTestSuite/test_parsing/y_string_uescaped_newline.json +1/−0
- tests/JSONTestSuite/test_parsing/y_string_unicode_U+10FFFE_nonchar.json +1/−0
- tests/JSONTestSuite/test_parsing/y_string_unicode_U+1FFFE_nonchar.json +1/−0
- tests/JSONTestSuite/test_parsing/y_string_unicode_U+FDD0_nonchar.json +1/−0
- tests/JSONTestSuite/test_parsing/y_string_unicode_U+FFFE_nonchar.json +1/−0
- tests/JSONTestSuite/test_parsing/y_string_utf16BE_no_BOM.json binary
- tests/JSONTestSuite/test_parsing/y_string_utf16LE_no_BOM.json binary
- tests/JSONTestSuite/test_transform/number_-9223372036854775808.json +1/−0
- tests/JSONTestSuite/test_transform/number_-9223372036854775809.json +1/−0
- tests/JSONTestSuite/test_transform/number_10000000000000000999.json +0/−0
- tests/JSONTestSuite/test_transform/number_1e-999.json +0/−0
- tests/JSONTestSuite/test_transform/number_1e6.json +0/−0
- tests/JSONTestSuite/test_transform/number_9223372036854775807.json +1/−0
- tests/JSONTestSuite/test_transform/number_9223372036854775808.json +1/−0
- tests/Regression/Issue967.hs +38/−0
- tests/Tests.hs +3/−1
- tests/UnitTests.hs +10/−73
aeson.cabal view
@@ -1,5 +1,5 @@ name: aeson-version: 2.1.0.0+version: 2.1.1.0 license: BSD3 license-file: LICENSE category: Text, Web, JSON@@ -18,7 +18,7 @@ || ==8.8.4 || ==8.10.7 || ==9.0.2- || ==9.2.3+ || ==9.2.4 || ==9.4.1 synopsis: Fast JSON parsing and encoding@@ -99,8 +99,8 @@ , bytestring >=0.10.8.1 && <0.12 , containers >=0.5.7.1 && <0.7 , deepseq >=1.4.2.0 && <1.5- , ghc-prim >=0.5.0.0 && <0.9- , template-haskell >=2.11.0.0 && <2.19+ , ghc-prim >=0.5.0.0 && <0.10+ , template-haskell >=2.11.0.0 && <2.20 , text >=1.2.3.0 && <1.3 || >=2.0 && <2.1 , time >=1.6.0.1 && <1.13 @@ -132,7 +132,7 @@ , these >=1.1.1.1 && <1.2 , unordered-containers >=0.2.10.0 && <0.3 , uuid-types >=1.0.5 && <1.1- , vector >=0.12.0.1 && <0.13+ , vector >=0.12.0.1 && <0.14 , witherable >=0.4.2 && <0.5 ghc-options: -Wall@@ -166,6 +166,7 @@ ErrorMessages Functions Instances+ JSONTestSuite Options Properties PropertyGeneric@@ -175,6 +176,7 @@ PropertyRTFunctors PropertyTH PropUtils+ Regression.Issue967 SerializationFormatSpec Types UnitTests
changelog.md view
@@ -1,5 +1,12 @@ 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.1.0++- Add `Data.Aeson.KeyMap.!?` (flipped) alias to `Data.Aeson.KeyMap.lookup`.+- Add `Data.Aeson.KeyMap.insertWith` function.+- Use `unsafeDupablePerformIO` instead of incorrect `accursedUnutterablePerformIO` in creation of keys in TH serialisation.+ This fixes a bug in TH deriving, e.g. when `Strict` pragma was enabled.+ ### 2.1.0.0 - Change time instances of types with year (`Day`, `UTCTime`) to require years with at least 4 digits.
src/Data/Aeson/Internal/ByteString.hs view
@@ -13,8 +13,8 @@ import Foreign.ForeignPtr (ForeignPtr) import Data.ByteString.Short (ShortByteString, fromShort) import GHC.Exts (Addr#, Ptr (Ptr))-import Data.ByteString.Internal (accursedUnutterablePerformIO) import Data.ByteString.Short.Internal (createFromPtr)+import System.IO.Unsafe (unsafeDupablePerformIO) import qualified Data.ByteString as BS import qualified Language.Haskell.TH.Lib as TH@@ -82,6 +82,7 @@ bs = fromShort sbs #endif +-- this is copied verbatim from @bytestring@, but only in recent versions. unsafePackLenLiteral :: Int -> Addr# -> ShortByteString unsafePackLenLiteral len addr# =- accursedUnutterablePerformIO $ createFromPtr (Ptr addr#) len+ unsafeDupablePerformIO $ createFromPtr (Ptr addr#) len
src/Data/Aeson/KeyMap.hs view
@@ -16,6 +16,7 @@ -- * Query null, lookup,+ (!?), size, member, @@ -25,6 +26,7 @@ -- ** Insertion insert,+ insertWith, -- * Deletion delete,@@ -190,6 +192,12 @@ insert :: Key -> v -> KeyMap v -> KeyMap v insert k v tm = KeyMap (M.insert k v (unKeyMap tm)) +-- | Insert with a function combining new and old values, taken in that order.+--+-- @since 2.1.1.0+insertWith :: (a -> a -> a) -> Key -> a -> KeyMap a -> KeyMap a+insertWith f k v m = KeyMap (M.insertWith f k v (unKeyMap m))+ -- | Map a function over all values in the map. map :: (a -> b) -> KeyMap a -> KeyMap b map = fmap@@ -393,6 +401,12 @@ insert :: Key -> v -> KeyMap v -> KeyMap v insert k v tm = KeyMap (H.insert k v (unKeyMap tm)) +-- | Insert with a function combining new and old values, taken in that order.+--+-- @since 2.1.1.0+insertWith :: (a -> a -> a) -> Key -> a -> KeyMap a -> KeyMap a+insertWith f k v m = KeyMap (H.insertWith f k v (unKeyMap m))+ -- | Map a function over all values in the map. map :: (a -> b) -> KeyMap a -> KeyMap b map = fmap@@ -548,6 +562,16 @@ ------------------------------------------------------------------------------- -- combinators using existing abstractions -------------------------------------------------------------------------------++-- | Return the value to which the specified key is mapped,+-- or Nothing if this map contains no mapping for the key.+--+-- This is a flipped version of 'lookup'.+--+-- @since 2.1.1.0+--+(!?) :: KeyMap v -> Key -> Maybe v+(!?) m k = lookup k m -- | Generalized union with combining function. alignWith :: (These a b -> c) -> KeyMap a -> KeyMap b -> KeyMap c
src/Data/Aeson/Parser/Internal.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} #if __GLASGOW_HASKELL__ <= 800 && __GLASGOW_HASKELL__ >= 706@@ -60,6 +61,7 @@ import Data.Scientific (Scientific) import Data.Text (Text) import Data.Vector (Vector)+import Data.Word (Word8) import qualified Data.Vector as Vector (empty, fromList, fromListN, reverse) import qualified Data.Attoparsec.ByteString as A import qualified Data.Attoparsec.Lazy as L@@ -77,22 +79,79 @@ -- >>> :set -XOverloadedStrings -- >>> import Data.Aeson.Types -#define BACKSLASH 92-#define CLOSE_CURLY 125-#define CLOSE_SQUARE 93-#define COMMA 44-#define DOUBLE_QUOTE 34-#define OPEN_CURLY 123-#define OPEN_SQUARE 91-#define C_0 48-#define C_9 57-#define C_A 65-#define C_F 70-#define C_a 97-#define C_f 102-#define C_n 110-#define C_t 116+-------------------------------------------------------------------------------+-- Word8 ASCII codes as patterns+------------------------------------------------------------------------------- +-- GHC-8.0 doesn't support giving multiple pattern synonyms type signature at once++-- spaces+pattern W8_SPACE :: Word8+pattern W8_NL :: Word8+pattern W8_CR :: Word8+pattern W8_TAB :: Word8++pattern W8_SPACE = 0x20+pattern W8_NL = 0x0a+pattern W8_CR = 0x0d+pattern W8_TAB = 0x09++-- punctuation+pattern W8_BACKSLASH :: Word8+pattern W8_DOUBLE_QUOTE :: Word8+pattern W8_DOT :: Word8+pattern W8_COMMA :: Word8++pattern W8_BACKSLASH = 92+pattern W8_COMMA = 44+pattern W8_DOT = 46+pattern W8_DOUBLE_QUOTE = 34++-- parentheses+pattern W8_CLOSE_CURLY :: Word8+pattern W8_CLOSE_SQUARE :: Word8+pattern W8_OPEN_SQUARE :: Word8+pattern W8_OPEN_CURLY :: Word8++pattern W8_OPEN_CURLY = 123+pattern W8_OPEN_SQUARE = 91+pattern W8_CLOSE_CURLY = 125+pattern W8_CLOSE_SQUARE = 93++-- operators+pattern W8_MINUS :: Word8+pattern W8_PLUS :: Word8++pattern W8_PLUS = 43+pattern W8_MINUS = 45++-- digits+pattern W8_0 :: Word8+pattern W8_9 :: Word8++pattern W8_0 = 48+pattern W8_9 = 57++-- lower case+pattern W8_e :: Word8+pattern W8_f :: Word8+pattern W8_n :: Word8+pattern W8_t :: Word8++pattern W8_e = 101+pattern W8_f = 102+pattern W8_n = 110+pattern W8_t = 116++-- upper case+pattern W8_E :: Word8+pattern W8_E = 69+++-------------------------------------------------------------------------------+-- Parsers+-------------------------------------------------------------------------------+ -- | Parse any JSON value. -- -- The conversion of a parsed value to a Haskell value is deferred@@ -151,7 +210,7 @@ objectValues mkObject str val = do skipSpace w <- A.peekWord8'- if w == CLOSE_CURLY+ if w == W8_CLOSE_CURLY then A.anyWord8 >> return KM.empty else loop [] where@@ -162,9 +221,9 @@ loop acc = do k <- (str A.<?> "object key") <* skipSpace <* (char ':' A.<?> "':'") v <- (val A.<?> "object value") <* skipSpace- ch <- A.satisfy (\w -> w == COMMA || w == CLOSE_CURLY) A.<?> "',' or '}'"+ ch <- A.satisfy (\w -> w == W8_COMMA || w == W8_CLOSE_CURLY) A.<?> "',' or '}'" let acc' = (k, v) : acc- if ch == COMMA+ if ch == W8_COMMA then skipSpace >> loop acc' else case mkObject acc' of Left err -> fail err@@ -185,14 +244,14 @@ arrayValues val = do skipSpace w <- A.peekWord8'- if w == CLOSE_SQUARE+ if w == W8_CLOSE_SQUARE then A.anyWord8 >> return Vector.empty else loop [] 1 where loop acc !len = do v <- (val A.<?> "json list value") <* skipSpace- ch <- A.satisfy (\w -> w == COMMA || w == CLOSE_SQUARE) A.<?> "',' or ']'"- if ch == COMMA+ ch <- A.satisfy (\w -> w == W8_COMMA || w == W8_CLOSE_SQUARE) A.<?> "',' or ']'"+ if ch == W8_COMMA then skipSpace >> loop (v:acc) (len+1) else return (Vector.reverse (Vector.fromListN len (v:acc))) {-# INLINE arrayValues #-}@@ -239,15 +298,15 @@ skipSpace w <- A.peekWord8' case w of- DOUBLE_QUOTE -> A.anyWord8 *> (String <$> jstring_)- 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"+ W8_DOUBLE_QUOTE -> A.anyWord8 *> (String <$> jstring_)+ W8_OPEN_CURLY -> A.anyWord8 *> object_ mkObject value_+ W8_OPEN_SQUARE -> A.anyWord8 *> array_ value_+ W8_f -> string "false" $> Bool False+ W8_t -> string "true" $> Bool True+ W8_n -> string "null" $> Null+ _ | w >= W8_0 && w <= W8_9 || w == W8_MINUS+ -> Number <$> scientific+ | otherwise -> fail "not a valid json value" {-# INLINE jsonWith #-} -- | Variant of 'json' which keeps only the last occurence of every key.@@ -291,19 +350,19 @@ skipSpace w <- A.peekWord8' case w of- DOUBLE_QUOTE -> do- !s <- A.anyWord8 *> jstring_- return (String s)- 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"+ W8_DOUBLE_QUOTE -> do+ !s <- A.anyWord8 *> jstring_+ return (String s)+ W8_OPEN_CURLY -> A.anyWord8 *> object_' mkObject value_+ W8_OPEN_SQUARE -> A.anyWord8 *> array_' value_+ W8_f -> string "false" $> Bool False+ W8_t -> string "true" $> Bool True+ W8_n -> string "null" $> Null+ _ | w >= W8_0 && w <= W8_9 || w == W8_MINUS+ -> 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.@@ -321,7 +380,7 @@ -- | Parse a quoted JSON string. jstring :: Parser Text-jstring = A.word8 DOUBLE_QUOTE *> jstring_+jstring = A.word8 W8_DOUBLE_QUOTE *> jstring_ -- | Parse a JSON Key key :: Parser Key@@ -331,16 +390,13 @@ jstring_ :: Parser Text {-# INLINE jstring_ #-} jstring_ = do- -- not sure whether >= or bit hackery is faster- -- perfectly, we shouldn't care, it's compiler job.- s <- A.takeWhile (\w -> w /= DOUBLE_QUOTE && w /= BACKSLASH && w >= 0x20 && w < 0x80)- let txt = unsafeDecodeASCII s+ s <- A.takeWhile (\w -> w /= W8_DOUBLE_QUOTE && w /= W8_BACKSLASH && w >= 0x20 && w < 0x80) mw <- A.peekWord8 case mw of- Nothing -> fail "string without end"- Just DOUBLE_QUOTE -> A.anyWord8 $> txt- Just w | w < 0x20 -> fail "unescaped control character"- _ -> jstringSlow s+ Nothing -> fail "string without end"+ Just W8_DOUBLE_QUOTE -> A.anyWord8 $> unsafeDecodeASCII s+ Just w | w < 0x20 -> fail "unescaped control character"+ _ -> jstringSlow s jstringSlow :: B.ByteString -> Parser Text {-# INLINE jstringSlow #-}@@ -350,13 +406,12 @@ Right r -> return r Left err -> fail $ show err where- startState = False+ startState = False go a c- | a = Just False- | c == DOUBLE_QUOTE = Nothing- | otherwise = let a' = c == backslash+ | a = Just False+ | c == W8_DOUBLE_QUOTE = Nothing+ | otherwise = let a' = c == W8_BACKSLASH in Just a'- where backslash = BACKSLASH decodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString -> Maybe a decodeWith p to s =@@ -451,7 +506,7 @@ -- | The only valid whitespace in a JSON document is space, newline, -- carriage return, and tab. skipSpace :: Parser ()-skipSpace = A.skipWhile $ \w -> w == 0x20 || w == 0x0a || w == 0x0d || w == 0x09+skipSpace = A.skipWhile $ \w -> w == W8_SPACE || w == W8_NL || w == W8_CR || w == W8_TAB {-# INLINE skipSpace #-} ------------------ Copy-pasted and adapted from attoparsec ------------------@@ -461,40 +516,34 @@ decimal0 :: Parser Integer decimal0 = do- let zero = 48 digits <- A.takeWhile1 isDigit_w8- if B.length digits > 1 && B.unsafeHead digits == zero+ if B.length digits > 1 && B.unsafeHead digits == W8_0 then fail "leading zero" else return (bsToInteger digits) -- | Parse a JSON number. scientific :: Parser Scientific scientific = do- let minus = 45- plus = 43 sign <- A.peekWord8'- let !positive = sign == plus || sign /= minus- when (sign == plus || sign == minus) $+ let !positive = not (sign == W8_MINUS)+ when (sign == W8_PLUS || sign == W8_MINUS) $ void A.anyWord8 n <- decimal0 let f fracDigits = SP (B.foldl' step n fracDigits) (negate $ B.length fracDigits)- step a w = a * 10 + fromIntegral (w - 48)+ step a w = a * 10 + fromIntegral (w - W8_0) dotty <- A.peekWord8- -- '.' -> ascii 46 SP c e <- case dotty of- Just 46 -> A.anyWord8 *> (f <$> A.takeWhile1 isDigit_w8)- _ -> pure (SP n 0)+ Just W8_DOT -> A.anyWord8 *> (f <$> A.takeWhile1 isDigit_w8)+ _ -> pure (SP n 0) let !signedCoeff | positive = c | otherwise = -c - let littleE = 101- bigE = 69- (A.satisfy (\ex -> ex == littleE || ex == bigE) *>+ (A.satisfy (\ex -> case ex of W8_e -> True; W8_E -> True; _ -> False) *> fmap (Sci.scientific signedCoeff . (e +)) (signed decimal)) <|> return (Sci.scientific signedCoeff e) {-# INLINE scientific #-}@@ -503,14 +552,14 @@ bsToInteger :: B.ByteString -> Integer bsToInteger bs- | l > 40 = valInteger 10 l [ fromIntegral (w - 48) | w <- B.unpack bs ]+ | l > 40 = valInteger 10 l [ fromIntegral (w - W8_0) | w <- B.unpack bs ] | otherwise = bsToIntegerSimple bs where l = B.length bs bsToIntegerSimple :: B.ByteString -> Integer bsToIntegerSimple = B.foldl' step 0 where- step a b = a * 10 + fromIntegral (b - 48) -- 48 = '0'+ step a b = a * 10 + fromIntegral (b - W8_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
+ tests/JSONTestSuite.hs view
@@ -0,0 +1,80 @@+module JSONTestSuite (tests) where++import Test.Tasty (TestTree, testGroup)+import Data.Either.Compat (isLeft, isRight)+import Test.Tasty.HUnit ( testCase, assertBool )+import System.Directory (getDirectoryContents)+import System.FilePath ((</>), takeExtension, takeFileName)+import Data.List (sort)+import Control.Monad (forM)++import qualified Data.ByteString.Lazy as L+import qualified Data.HashSet as HashSet++import Data.Aeson++jsonTestSuiteTest :: FilePath -> TestTree+jsonTestSuiteTest path = testCase fileName $ do+ payload <- L.readFile path+ let result = eitherDecode payload :: Either String Value+ assertBool (show result) $ case take 2 fileName of+ "n_" -> isLeft result+ "y_" -> isRight result+ "i_" | fileName `HashSet.member` ignore_accepted -> isRight result+ | otherwise -> isLeft result+ _ | fileName `HashSet.member` transform_rejected -> isLeft result+ | otherwise -> isRight result -- test_transform tests have inconsistent names+ where+ fileName = takeFileName path++-- Build a collection of tests based on the current contents of the+-- JSONTestSuite test directories.++tests :: IO TestTree+tests = do+ let suitePath = "tests/JSONTestSuite"+ let suites = ["test_parsing", "test_transform"]+ testPaths <- fmap (sort . concat) . forM suites $ \suite -> do+ let dir = suitePath </> suite+ entries <- getDirectoryContents dir+ let ok name = takeExtension name == ".json"+ return . map (dir </>) . filter ok $ entries+ return $ testGroup "JSONTestSuite" $ map jsonTestSuiteTest testPaths++-- The set expected-to-be-failing JSONTestSuite tests.+-- Not all of these failures are genuine bugs.+-- Of those that are bugs, not all are worth fixing.++-- | The @i@ cases we can ignore. We don't.+--+-- @i_@ - parsers are free to accept or reject content+--+-- We specify which @i_@ case we accept, so we can catch changes even in unspecified behavior.+-- (There is less case we accept)+ignore_accepted :: HashSet.HashSet FilePath+ignore_accepted = HashSet.fromList+ [ "i_number_double_huge_neg_exp.json"+ , "i_number_huge_exp.json"+ , "i_number_neg_int_huge_exp.json"+ , "i_number_pos_double_huge_exp.json"+ , "i_number_real_neg_overflow.json"+ , "i_number_real_pos_overflow.json"+ , "i_number_real_underflow.json"+ , "i_number_too_big_neg_int.json"+ , "i_number_too_big_pos_int.json"+ , "i_number_very_big_negative_int.json"+ , "i_structure_500_nested_arrays.json"+ ]++-- | Transform folder contain weird structures and characters that parsers may understand differently.+--+-- We don't even try to understand some.+transform_rejected :: HashSet.HashSet FilePath+transform_rejected = HashSet.fromList+ [ "string_1_escaped_invalid_codepoint.json"+ , "string_1_invalid_codepoint.json"+ , "string_2_escaped_invalid_codepoints.json"+ , "string_2_invalid_codepoints.json"+ , "string_3_escaped_invalid_codepoints.json"+ , "string_3_invalid_codepoints.json"+ ]
+ tests/JSONTestSuite/test_parsing/i_number_double_huge_neg_exp.json view
@@ -0,0 +1,1 @@+[123.456e-789]
+ tests/JSONTestSuite/test_parsing/i_number_huge_exp.json view
@@ -0,0 +1,1 @@+[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]
+ tests/JSONTestSuite/test_parsing/i_number_neg_int_huge_exp.json view
@@ -0,0 +1,1 @@+[-1e+9999]
+ tests/JSONTestSuite/test_parsing/i_number_pos_double_huge_exp.json view
@@ -0,0 +1,1 @@+[1.5e+9999]
+ tests/JSONTestSuite/test_parsing/i_number_real_neg_overflow.json view
@@ -0,0 +1,1 @@+[-123123e100000]
+ tests/JSONTestSuite/test_parsing/i_number_real_pos_overflow.json view
@@ -0,0 +1,1 @@+[123123e100000]
+ tests/JSONTestSuite/test_parsing/i_number_real_underflow.json view
@@ -0,0 +1,1 @@+[123e-10000000]
+ tests/JSONTestSuite/test_parsing/i_number_too_big_neg_int.json view
@@ -0,0 +1,1 @@+[-123123123123123123123123123123]
+ tests/JSONTestSuite/test_parsing/i_number_too_big_pos_int.json view
@@ -0,0 +1,1 @@+[100000000000000000000]
+ tests/JSONTestSuite/test_parsing/i_number_very_big_negative_int.json view
@@ -0,0 +1,1 @@+[-237462374673276894279832749832423479823246327846]
+ tests/JSONTestSuite/test_parsing/i_string_UTF8_surrogate_U+D800.json view
@@ -0,0 +1,1 @@+["í "]
+ tests/JSONTestSuite/test_parsing/i_string_invalid_utf-8.json view
@@ -0,0 +1,1 @@+["ÿ"]
+ tests/JSONTestSuite/test_parsing/i_string_iso_latin_1.json view
@@ -0,0 +1,1 @@+["é"]
+ tests/JSONTestSuite/test_parsing/i_string_lone_utf8_continuation_byte.json view
@@ -0,0 +1,1 @@+[""]
+ tests/JSONTestSuite/test_parsing/i_string_overlong_sequence_2_bytes.json view
@@ -0,0 +1,1 @@+["À¯"]
+ tests/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes.json view
@@ -0,0 +1,1 @@+["ü¿¿¿¿"]
+ tests/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes_null.json view
@@ -0,0 +1,1 @@+["ü"]
− tests/JSONTestSuite/test_parsing/i_string_unicode_U+10FFFE_nonchar.json
@@ -1,1 +0,0 @@-["\uDBFF\uDFFE"]
− tests/JSONTestSuite/test_parsing/i_string_unicode_U+1FFFE_nonchar.json
@@ -1,1 +0,0 @@-["\uD83F\uDFFE"]
− tests/JSONTestSuite/test_parsing/i_string_unicode_U+FDD0_nonchar.json
@@ -1,1 +0,0 @@-["\uFDD0"]
− tests/JSONTestSuite/test_parsing/i_string_unicode_U+FFFE_nonchar.json
@@ -1,1 +0,0 @@-["\uFFFE"]
+ tests/JSONTestSuite/test_parsing/i_string_utf16BE_no_BOM.json view
binary file changed (absent → 10 bytes)
+ tests/JSONTestSuite/test_parsing/i_string_utf16LE_no_BOM.json view
binary file changed (absent → 10 bytes)
+ tests/JSONTestSuite/test_parsing/n_multidigit_number_then_00.json view
binary file changed (absent → 4 bytes)
− tests/JSONTestSuite/test_parsing/n_number_then_00.json
binary file changed (2 → absent bytes)
+ tests/JSONTestSuite/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json view
@@ -0,0 +1,1 @@+{"¹":"0",}
− tests/JSONTestSuite/test_parsing/n_object_pi_in_key_and_trailing_comma.json
@@ -1,1 +0,0 @@-{"¹":"0",}
− tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape u.json
@@ -1,1 +0,0 @@-["\uD800\u"]
− tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape u1.json
@@ -1,1 +0,0 @@-["\uD800\u1"]
− tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape u1x.json
@@ -1,1 +0,0 @@-["\uD800\u1x"]
+ tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u.json view
@@ -0,0 +1,1 @@+["\uD800\u"]
+ tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1.json view
@@ -0,0 +1,1 @@+["\uD800\u1"]
+ tests/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1x.json view
@@ -0,0 +1,1 @@+["\uD800\u1x"]
− tests/JSONTestSuite/test_parsing/n_string_UTF8_surrogate_U+D800.json
@@ -1,1 +0,0 @@-["í "]
− tests/JSONTestSuite/test_parsing/n_string_invalid_utf-8.json
@@ -1,1 +0,0 @@-["ÿ"]
− tests/JSONTestSuite/test_parsing/n_string_iso_latin_1.json
@@ -1,1 +0,0 @@-["é"]
− tests/JSONTestSuite/test_parsing/n_string_lone_utf8_continuation_byte.json
@@ -1,1 +0,0 @@-[""]
− tests/JSONTestSuite/test_parsing/n_string_overlong_sequence_2_bytes.json
@@ -1,1 +0,0 @@-["À¯"]
− tests/JSONTestSuite/test_parsing/n_string_overlong_sequence_6_bytes.json
@@ -1,1 +0,0 @@-["ü¿¿¿¿"]
− tests/JSONTestSuite/test_parsing/n_string_overlong_sequence_6_bytes_null.json
@@ -1,1 +0,0 @@-["ü"]
− tests/JSONTestSuite/test_parsing/n_string_unescaped_crtl_char.json
binary file changed (7 → absent bytes)
+ tests/JSONTestSuite/test_parsing/n_string_unescaped_ctrl_char.json view
binary file changed (absent → 7 bytes)
+ tests/JSONTestSuite/test_parsing/n_structure_angle_bracket_null.json view
@@ -0,0 +1,1 @@+[<null>]
− tests/JSONTestSuite/test_parsing/n_structure_null.json
@@ -1,1 +0,0 @@-[<null>]
+ tests/JSONTestSuite/test_parsing/n_structure_single_eacute.json view
@@ -0,0 +1,1 @@+é
− tests/JSONTestSuite/test_parsing/n_structure_single_point.json
@@ -1,1 +0,0 @@-é
− tests/JSONTestSuite/test_parsing/y_number_double_huge_neg_exp.json
@@ -1,1 +0,0 @@-[123.456e-789]
− tests/JSONTestSuite/test_parsing/y_number_huge_exp.json
@@ -1,1 +0,0 @@-[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]
− tests/JSONTestSuite/test_parsing/y_number_neg_int_huge_exp.json
@@ -1,1 +0,0 @@-[-1e+9999]
− tests/JSONTestSuite/test_parsing/y_number_pos_double_huge_exp.json
@@ -1,1 +0,0 @@-[1.5e+9999]
− tests/JSONTestSuite/test_parsing/y_number_real_neg_overflow.json
@@ -1,1 +0,0 @@-[-123123e100000]
− tests/JSONTestSuite/test_parsing/y_number_real_pos_overflow.json
@@ -1,1 +0,0 @@-[123123e100000]
− tests/JSONTestSuite/test_parsing/y_number_real_underflow.json
@@ -1,1 +0,0 @@-[123e-10000000]
− tests/JSONTestSuite/test_parsing/y_number_too_big_neg_int.json
@@ -1,1 +0,0 @@-[-123123123123123123123123123123]
− tests/JSONTestSuite/test_parsing/y_number_too_big_pos_int.json
@@ -1,1 +0,0 @@-[100000000000000000000]
− tests/JSONTestSuite/test_parsing/y_number_very_big_negative_int.json
@@ -1,1 +0,0 @@-[-237462374673276894279832749832423479823246327846]
tests/JSONTestSuite/test_parsing/y_object_extreme_numbers.json view
+ tests/JSONTestSuite/test_parsing/y_string_nbsp_uescaped.json view
@@ -0,0 +1,1 @@+["new\u00A0line"]
− tests/JSONTestSuite/test_parsing/y_string_newline_uescaped.json
@@ -1,1 +0,0 @@-["new\u00A0line"]
− tests/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json
@@ -1,1 +0,0 @@-[""]
+ tests/JSONTestSuite/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json view
@@ -0,0 +1,1 @@+[""]
+ tests/JSONTestSuite/test_parsing/y_string_uescaped_newline.json view
@@ -0,0 +1,1 @@+["new\u000Aline"]
+ tests/JSONTestSuite/test_parsing/y_string_unicode_U+10FFFE_nonchar.json view
@@ -0,0 +1,1 @@+["\uDBFF\uDFFE"]
+ tests/JSONTestSuite/test_parsing/y_string_unicode_U+1FFFE_nonchar.json view
@@ -0,0 +1,1 @@+["\uD83F\uDFFE"]
+ tests/JSONTestSuite/test_parsing/y_string_unicode_U+FDD0_nonchar.json view
@@ -0,0 +1,1 @@+["\uFDD0"]
+ tests/JSONTestSuite/test_parsing/y_string_unicode_U+FFFE_nonchar.json view
@@ -0,0 +1,1 @@+["\uFFFE"]
− tests/JSONTestSuite/test_parsing/y_string_utf16BE_no_BOM.json
binary file changed (10 → absent bytes)
− tests/JSONTestSuite/test_parsing/y_string_utf16LE_no_BOM.json
binary file changed (10 → absent bytes)
+ tests/JSONTestSuite/test_transform/number_-9223372036854775808.json view
@@ -0,0 +1,1 @@+[-9223372036854775808]
+ tests/JSONTestSuite/test_transform/number_-9223372036854775809.json view
@@ -0,0 +1,1 @@+[-9223372036854775809]
tests/JSONTestSuite/test_transform/number_10000000000000000999.json view
tests/JSONTestSuite/test_transform/number_1e-999.json view
tests/JSONTestSuite/test_transform/number_1e6.json view
+ tests/JSONTestSuite/test_transform/number_9223372036854775807.json view
@@ -0,0 +1,1 @@+[9223372036854775807]
+ tests/JSONTestSuite/test_transform/number_9223372036854775808.json view
@@ -0,0 +1,1 @@+[9223372036854775808]
+ tests/Regression/Issue967.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE Strict #-}+{-# LANGUAGE TemplateHaskell #-}+-- {-# OPTIONS_GHC -ddump-splices #-}+-- {-# OPTIONS_GHC -ddump-simpl -ddump-to-file #-}+module Regression.Issue967 (issue967) where++import Test.Tasty (TestTree)+import Test.Tasty.HUnit (testCase, assertEqual)++import qualified Data.Text.Lazy as LT+import qualified Data.Text.Lazy.Encoding as LTE++import Data.Aeson+import Data.Aeson.TH++data DataA = DataA+ { val1 :: Int,+ val2 :: Int+ }+ deriving (Eq, Show)++-------------------------------------------------------------------------------+-- Instances+-------------------------------------------------------------------------------++$(deriveJSON defaultOptions ''DataA)++-------------------------------------------------------------------------------+-- Test+-------------------------------------------------------------------------------++issue967 :: TestTree+issue967 = testCase "issue967" $ do+ let ev = DataA 1 2+ encoding = encode ev+ parsedEv = decode encoding :: Maybe DataA++ assertEqual (LT.unpack $ LTE.decodeUtf8 encoding) (Just ev) parsedEv
tests/Tests.hs view
@@ -8,9 +8,11 @@ import qualified DataFamilies.Properties as DF import qualified Properties import qualified UnitTests+import qualified JSONTestSuite main :: IO () main = do ioTests <- UnitTests.ioTests- let allTests = DF.tests : Properties.tests : UnitTests.tests : ioTests+ jsTests <- JSONTestSuite.tests+ let allTests = DF.tests : Properties.tests : UnitTests.tests : jsTests : ioTests defaultMain (testGroup "tests" allTests)
tests/UnitTests.hs view
@@ -49,10 +49,9 @@ import qualified Data.Aeson.KeyMap as KM import Data.Attoparsec.ByteString (Parser, parseOnly) import Data.Char (toUpper, GeneralCategory(Control,Surrogate), generalCategory)-import Data.Either.Compat (isLeft, isRight) import Data.Hashable (hash) import Data.HashMap.Strict (HashMap)-import Data.List (sort, isSuffixOf)+import Data.List (isSuffixOf) import Data.Maybe (fromMaybe) import Data.Scientific (Scientific, scientific) import Data.Tagged (Tagged(..))@@ -63,16 +62,13 @@ import GHC.Generics.Generically (Generically (..)) 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, 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.Text.Lazy as LT import qualified Data.Text.Lazy.Builder as TLB import qualified Data.Text.Lazy.Encoding as LT@@ -81,6 +77,7 @@ import qualified ErrorMessages import qualified SerializationFormatSpec import qualified Data.Map as Map -- Lazy!+import Regression.Issue967 roundTripCamel :: String -> Assertion roundTripCamel name = assertEqual "" name (camelFrom '_' $ camelTo '_' name)@@ -419,73 +416,8 @@ Surrogate -> False _ -> True --- JSONTestSuite -jsonTestSuiteTest :: FilePath -> TestTree-jsonTestSuiteTest path = testCase fileName $ do- payload <- L.readFile path- let result = eitherDecode payload :: Either String Value- assertBool fileName $ case take 2 fileName of- "i_" -> isRight result- "n_" -> isLeft result- "y_" -> isRight result- _ -> isRight result -- test_transform tests have inconsistent names- where- fileName = takeFileName path --- Build a collection of tests based on the current contents of the--- JSONTestSuite test directories.--jsonTestSuite :: IO TestTree-jsonTestSuite = do- let suitePath = "tests/JSONTestSuite"- let suites = ["test_parsing", "test_transform"]- testPaths <- fmap (sort . concat) . forM suites $ \suite -> do- let dir = suitePath </> suite- entries <- getDirectoryContents dir- let ok name = takeExtension name == ".json" &&- not (name `HashSet.member` blacklist)- return . map (dir </>) . filter ok $ entries- return $ testGroup "JSONTestSuite" $ map jsonTestSuiteTest testPaths---- The set expected-to-be-failing JSONTestSuite tests.--- Not all of these failures are genuine bugs.--- Of those that are bugs, not all are worth fixing.--blacklist :: HashSet.HashSet String--- blacklist = HashSet.empty-blacklist = _blacklist--_blacklist :: HashSet.HashSet String-_blacklist = HashSet.fromList [- "i_object_key_lone_2nd_surrogate.json"- , "i_string_1st_surrogate_but_2nd_missing.json"- , "i_string_1st_valid_surrogate_2nd_invalid.json"- , "i_string_UTF-16LE_with_BOM.json"- , "i_string_UTF-16_invalid_lonely_surrogate.json"- , "i_string_UTF-16_invalid_surrogate.json"- , "i_string_UTF-8_invalid_sequence.json"- , "i_string_incomplete_surrogate_and_escape_valid.json"- , "i_string_incomplete_surrogate_pair.json"- , "i_string_incomplete_surrogates_escape_valid.json"- , "i_string_invalid_lonely_surrogate.json"- , "i_string_invalid_surrogate.json"- , "i_string_inverted_surrogates_U+1D11E.json"- , "i_string_lone_second_surrogate.json"- , "i_string_not_in_unicode_range.json"- , "i_string_truncated-utf-8.json"- , "i_structure_UTF-8_BOM_empty_object.json"- , "string_1_escaped_invalid_codepoint.json"- , "string_1_invalid_codepoint.json"- , "string_1_invalid_codepoints.json"- , "string_2_escaped_invalid_codepoints.json"- , "string_2_invalid_codepoints.json"- , "string_3_escaped_invalid_codepoints.json"- , "string_3_invalid_codepoints.json"- , "y_string_utf16BE_no_BOM.json"- , "y_string_utf16LE_no_BOM.json"- ]- -- A regression test for: https://github.com/bos/aeson/pull/455 data Foo a = FooNil | FooCons (Foo Int) deriveToJSON defaultOptions ''Foo@@ -838,8 +770,7 @@ ioTests :: IO [TestTree] ioTests = do enc <- encoderComparisonTests- js <- jsonTestSuite- return [enc, js]+ return [enc] tests :: TestTree tests = testGroup "unit" [@@ -894,4 +825,10 @@ assertEqual "" (object ["foo" .= True]) [aesonQQ| {"foo": true } |] ] , monadFixTests+ , issue967+ , testCase "KeyMap.insertWith" $ do+ KM.insertWith (-) "a" 2 (KM.fromList [("a", 1)]) @?= KM.fromList [("a",1 :: Int)]+ KM.insertWith (flip (-)) "a" 2 (KM.fromList [("a", 1)]) @?= KM.fromList [("a",-1 :: Int)]+ KM.insertWith (-) "b" 2 (KM.fromList [("a", 1)]) @?= KM.fromList [("a",1),("b",2 :: Int)]+ KM.insertWith (-) "b" 2 KM.empty @?= KM.fromList [("b",2 :: Int)] ]