packages feed

aeson 1.0.2.1 → 1.1.0.0

raw patch · 19 files changed

+680/−247 lines, 19 filesdep +directorydep +filepathdep +uuid-typesdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: directory, filepath, uuid-types

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Data.Aeson.Encoding: pair' :: Encoding' Text -> Encoding -> Series
+ Data.Aeson.Encoding.Internal: pair' :: Encoding' Text -> Encoding -> Series
+ Data.Aeson.Types: explicitParseField :: (Value -> Parser a) -> Object -> Text -> Parser a
+ Data.Aeson.Types: explicitParseFieldMaybe :: (Value -> Parser a) -> Object -> Text -> Parser (Maybe a)
+ Data.Aeson.Types: explicitParseFieldMaybe' :: (Value -> Parser a) -> Object -> Text -> Parser (Maybe a)
+ Data.Aeson.Types: parseField :: (FromJSON a) => Object -> Text -> Parser a
+ Data.Aeson.Types: parseFieldMaybe :: (FromJSON a) => Object -> Text -> Parser (Maybe a)
+ Data.Aeson.Types: parseFieldMaybe' :: (FromJSON a) => Object -> Text -> Parser (Maybe a)

Files

Data/Aeson/Encoding.hs view
@@ -14,6 +14,7 @@     , Series     , pairs     , pair+    , pair'     -- * Predicates     , nullEncoding     -- * Encoding constructors
Data/Aeson/Encoding/Builder.hs view
@@ -193,12 +193,13 @@         !(T dh dl)  = twoDigits d         encodeYear y             | y >= 1000 = B.integerDec y-            | y > 0 =-                let (ab,c) = fromIntegral y `quotRem` 10-                    (a,b)  = ab `quotRem` 10-                in BP.primBounded (ascii4 ('0',(digit a,(digit b,digit c)))) ()-            | otherwise =-                error "Data.Aeson.Encode.Builder.day:  years BCE not supported"+            | y >= 0    = BP.primBounded (ascii4 (padYear y)) ()+            | y >= -999 = BP.primBounded (ascii5 ('-',padYear (- y))) ()+            | otherwise = B.integerDec y+        padYear y =+            let (ab,c) = fromIntegral y `quotRem` 10+                (a,b)  = ab `quotRem` 10+            in ('0',(digit a,(digit b,digit c))) {-# INLINE day #-}  timeOfDay :: TimeOfDay -> Builder@@ -305,7 +306,7 @@               where                 go !i !op                   | i < iendTmp = case A.unsafeIndex arr i of-                      w | w <= 0x7F -> do+                      w | w <= 0x7F ->                             BP.runB be (fromIntegral w) op >>= go (i + 1)                         | w <= 0x7FF -> do                             poke8 0 $ (w `shiftR` 6) + 0xC0
Data/Aeson/Encoding/Internal.hs view
@@ -13,6 +13,7 @@     , Series (..)     , pairs     , pair+    , pair'     -- * Predicates     , nullEncoding     -- * Encoding constructors@@ -122,7 +123,10 @@             deriving (Typeable)  pair :: Text -> Encoding -> Series-pair name val = Value $ retagEncoding $ text name >< colon >< val+pair name val = pair' (text name) val++pair' :: Encoding' Text -> Encoding -> Series+pair' name val = Value $ retagEncoding $ retagEncoding name >< colon >< val  instance Semigroup Series where     Empty   <> a       = a
Data/Aeson/Parser/Internal.hs view
@@ -36,15 +36,20 @@ import Prelude () import Prelude.Compat +import Control.Applicative ((<|>))+import Control.Monad (void, when) import Data.Aeson.Types.Internal (IResult(..), JSONPath, Result(..), Value(..))-import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, scientific, skipSpace, string)+import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)+import Data.Scientific (Scientific) import Data.Text (Text) import Data.Vector as Vector (Vector, empty, fromListN, reverse) import qualified Data.Attoparsec.ByteString as A import qualified Data.Attoparsec.Lazy as L import qualified Data.ByteString as B+import qualified Data.ByteString.Unsafe as B import qualified Data.ByteString.Lazy as L import qualified Data.HashMap.Strict as H+import qualified Data.Scientific as Sci import Data.Aeson.Parser.Unescape  #if MIN_VERSION_ghc_prim(0,3,1)@@ -289,3 +294,54 @@ -- end-of-input.  See also: 'json''. jsonEOF' :: Parser Value jsonEOF' = json' <* skipSpace <* endOfInput++-- | 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+{-# INLINE skipSpace #-}++------------------ Copy-pasted and adapted from attoparsec ------------------++-- A strict pair+data SP = SP !Integer {-# UNPACK #-}!Int++decimal0 :: Parser Integer+decimal0 = do+  let step a w = a * 10 + fromIntegral (w - zero)+      zero = 48+  digits <- A.takeWhile1 isDigit_w8+  if B.length digits > 1 && B.unsafeHead digits == zero+    then fail "leading zero"+    else return (B.foldl' step 0 digits)++{-# INLINE scientific #-}+scientific :: Parser Scientific+scientific = do+  let minus = 45+      plus  = 43+  sign <- A.peekWord8'+  let !positive = sign == plus || sign /= minus+  when (sign == plus || sign == 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)++  dotty <- A.peekWord8+  -- '.' -> ascii 46+  SP c e <- case dotty of+              Just 46 -> 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) *>+      fmap (Sci.scientific signedCoeff . (e +)) (signed decimal)) <|>+    return (Sci.scientific signedCoeff    e)
Data/Aeson/Parser/Time.hs view
@@ -25,6 +25,7 @@ import Prelude () import Prelude.Compat +import Control.Applicative ((<|>)) import Control.Monad (void, when) import Data.Aeson.Internal.Time (toPico) import Data.Attoparsec.Text as A@@ -46,13 +47,14 @@             Left err -> fail $ "could not parse date: " ++ err             Right r  -> return r --- | Parse a date of the form @YYYY-MM-DD@.+-- | Parse a date of the form @[+,-]YYYY-MM-DD@. day :: Parser Day day = do+  absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id   y <- decimal <* char '-'   m <- twoDigits <* char '-'   d <- twoDigits-  maybe (fail "invalid date") return (fromGregorianValid y m d)+  maybe (fail "invalid date") return (fromGregorianValid (absOrNeg y) m d)  -- | Parse a two-digit integer (e.g. day of month, hour). twoDigits :: Parser Int
Data/Aeson/Types.hs view
@@ -90,6 +90,12 @@     , (.:!)     , (.!=)     , object+    , parseField+    , parseFieldMaybe+    , parseFieldMaybe'+    , explicitParseField+    , explicitParseFieldMaybe+    , explicitParseFieldMaybe'      , listEncoding     , listValue
Data/Aeson/Types/Class.hs view
@@ -76,11 +76,18 @@     -- * Functions     , fromJSON     , ifromJSON+    , typeMismatch+    , parseField+    , parseFieldMaybe+    , parseFieldMaybe'+    , explicitParseField+    , explicitParseFieldMaybe+    , explicitParseFieldMaybe'+    -- ** Operators     , (.:)     , (.:?)     , (.:!)     , (.!=)-    , typeMismatch     ) where  import Prelude ()
Data/Aeson/Types/FromJSON.hs view
@@ -59,11 +59,18 @@     -- * Functions     , fromJSON     , ifromJSON+    , typeMismatch+    , parseField+    , parseFieldMaybe+    , parseFieldMaybe'+    , explicitParseField+    , explicitParseFieldMaybe+    , explicitParseFieldMaybe'+    -- ** Operators     , (.:)     , (.:?)     , (.:!)     , (.!=)-    , typeMismatch      -- * Internal     , parseOptionalFieldWith@@ -123,6 +130,7 @@ import qualified Data.Text.Encoding as T import qualified Data.Text.Lazy as LT import qualified Data.Tree as Tree+import qualified Data.UUID.Types as UUID import qualified Data.Vector as V import qualified Data.Vector.Generic as VG import qualified Data.Vector.Primitive as VP@@ -653,9 +661,7 @@ -- in an object for it to be valid.  If the key and value are -- optional, use '.:?' instead. (.:) :: (FromJSON a) => Object -> Text -> Parser a-obj .: key = case H.lookup key obj of-               Nothing -> fail $ "key " ++ show key ++ " not present"-               Just v  -> parseJSON v <?> Key key+(.:) = explicitParseField parseJSON {-# INLINE (.:) #-}  -- | Retrieve the value associated with the given key of an 'Object'. The@@ -666,9 +672,7 @@ -- from an object without affecting its validity.  If the key and -- value are mandatory, use '.:' instead. (.:?) :: (FromJSON a) => Object -> Text -> Parser (Maybe a)-obj .:? key = case H.lookup key obj of-               Nothing -> pure Nothing-               Just v  -> parseJSON v <?> Key key+(.:?) = explicitParseFieldMaybe parseJSON {-# INLINE (.:?) #-}  -- | Retrieve the value associated with the given key of an 'Object'.@@ -678,11 +682,47 @@ -- This differs from '.:?' by attempting to parse 'Null' the same as any -- other JSON value, instead of interpreting it as 'Nothing'. (.:!) :: (FromJSON a) => Object -> Text -> Parser (Maybe a)-obj .:! key = case H.lookup key obj of-               Nothing -> pure Nothing-               Just v  -> Just <$> parseJSON v <?> Key key+(.:!) = explicitParseFieldMaybe' parseJSON {-# INLINE (.:!) #-} +-- | Function variant of '.:'.+parseField :: (FromJSON a) => Object -> Text -> Parser a+parseField = (.:)+{-# INLINE parseField #-}++-- | Function variant of '.:?'.+parseFieldMaybe :: (FromJSON a) => Object -> Text -> Parser (Maybe a)+parseFieldMaybe = (.:?)+{-# INLINE parseFieldMaybe #-}++-- | Function variant of '.:!'.+parseFieldMaybe' :: (FromJSON a) => Object -> Text -> Parser (Maybe a)+parseFieldMaybe' = (.:!)+{-# INLINE parseFieldMaybe' #-}++-- | Variant of '.:' with explicit parser function.+--+-- E.g. @'explicitParseField' 'parseJSON1' :: ('FromJSON1' f, 'FromJSON' a) -> 'Object' -> 'Text' -> 'Parser' (f a)@+explicitParseField :: (Value -> Parser a) -> Object -> Text -> Parser a+explicitParseField p obj key = case H.lookup key obj of+    Nothing -> fail $ "key " ++ show key ++ " not present"+    Just v  -> p v <?> Key key+{-# INLINE explicitParseField #-}++-- | Variant of '.:?' with explicit parser function.+explicitParseFieldMaybe :: (Value -> Parser a) -> Object -> Text -> Parser (Maybe a)+explicitParseFieldMaybe p obj key = case H.lookup key obj of+    Nothing -> pure Nothing+    Just v  -> liftParseJSON p (listParser p) v <?> Key key -- listParser isn't used by maybe instance.+{-# INLINE explicitParseFieldMaybe #-}++-- | Variant of '.:!' with explicit parser function.+explicitParseFieldMaybe' :: (Value -> Parser a) -> Object -> Text -> Parser (Maybe a)+explicitParseFieldMaybe' p obj key = case H.lookup key obj of+    Nothing -> pure Nothing+    Just v  -> Just <$> p v <?> Key key+{-# INLINE explicitParseFieldMaybe' #-}+ -- | Helper for use in combination with '.:?' to provide default -- values for optional JSON object fields. --@@ -1360,7 +1400,7 @@     parseJSONList = liftParseJSONList parseJSON parseJSONList     {-# INLINE parseJSONList #-} -instance (FromJSONKey a, FromJSON a) => FromJSONKey (Identity a) where+instance (FromJSONKey a) => FromJSONKey (Identity a) where     fromJSONKey = coerceFromJSONKeyFunction (fromJSONKey :: FromJSONKeyFunction a)     fromJSONKeyList = coerceFromJSONKeyFunction (fromJSONKeyList :: FromJSONKeyFunction [a]) @@ -1487,6 +1527,18 @@     {-# INLINE parseJSON #-}  -------------------------------------------------------------------------------+-- uuid+-------------------------------------------------------------------------------++instance FromJSON UUID.UUID where+    parseJSON = withText "UUID" $+        maybe (fail "Invalid UUID") pure . UUID.fromText++instance FromJSONKey UUID.UUID where+    fromJSONKey = FromJSONKeyTextParser $+        maybe (fail "Invalid UUID") pure . UUID.fromText++------------------------------------------------------------------------------- -- vector ------------------------------------------------------------------------------- @@ -1725,11 +1777,20 @@ -- tagged ------------------------------------------------------------------------------- +instance FromJSON1 Proxy where+    {-# INLINE liftParseJSON #-}+    liftParseJSON _ _ Null = pure Proxy+    liftParseJSON _ _ v    = typeMismatch "Proxy" v+ instance FromJSON (Proxy a) where     {-# INLINE parseJSON #-}     parseJSON Null = pure Proxy     parseJSON v    = typeMismatch "Proxy" v ++instance FromJSON2 Tagged where+    liftParseJSON2 _ _ p _ = fmap Tagged . p+    {-# INLINE liftParseJSON2 #-}  instance FromJSON1 (Tagged a) where     liftParseJSON p _ = fmap Tagged . p
Data/Aeson/Types/ToJSON.hs view
@@ -113,6 +113,7 @@ import qualified Data.Text.Encoding as T import qualified Data.Text.Lazy as LT import qualified Data.Tree as Tree+import qualified Data.UUID.Types as UUID import qualified Data.Vector as V import qualified Data.Vector.Generic as VG import qualified Data.Vector.Mutable as VM@@ -120,6 +121,9 @@ import qualified Data.Vector.Storable as VS import qualified Data.Vector.Unboxed as VU +import qualified Data.Aeson.Encoding.Builder as EB+import qualified Data.ByteString.Builder as B+ #if !(MIN_VERSION_bytestring(0,10,0)) import Foreign.ForeignPtr (withForeignPtr) import Foreign.Marshal.Utils (copyBytes)@@ -615,6 +619,11 @@     {-# INLINE liftToEncoding #-}  instance (ToJSON a) => ToJSON [a] where+    {-# SPECIALIZE instance ToJSON String #-}+    {-# SPECIALIZE instance ToJSON [String] #-}+    {-# SPECIALIZE instance ToJSON [Array] #-}+    {-# SPECIALIZE instance ToJSON [Object] #-}+     toJSON = toJSON1     {-# INLINE toJSON #-} @@ -1661,7 +1670,7 @@     toEncodingList = liftToEncodingList toEncoding toEncodingList     {-# INLINE toEncodingList #-} -instance (ToJSONKey a, ToJSON a) => ToJSONKey (Identity a) where+instance (ToJSONKey a) => ToJSONKey (Identity a) where     toJSONKey = contramapToJSONKeyFunction runIdentity toJSONKey     toJSONKeyList = contramapToJSONKeyFunction (map runIdentity) toJSONKeyList @@ -1852,7 +1861,18 @@     toEncoding = toEncoding1     {-# INLINE toEncoding #-} +-------------------------------------------------------------------------------+-- uuid+------------------------------------------------------------------------------- +instance ToJSON UUID.UUID where+    toJSON = toJSON . UUID.toText+    toEncoding = E.unsafeToEncoding . EB.quote . B.byteString . UUID.toASCIIBytes++instance ToJSONKey UUID.UUID where+    toJSONKey = ToJSONKeyText UUID.toText $+        E.unsafeToEncoding . EB.quote . B.byteString . UUID.toASCIIBytes+ ------------------------------------------------------------------------------- -- vector -------------------------------------------------------------------------------@@ -1865,6 +1885,8 @@     {-# INLINE liftToEncoding #-}  instance (ToJSON a) => ToJSON (Vector a) where+    {-# SPECIALIZE instance ToJSON Array #-}+     toJSON = toJSON1     {-# INLINE toJSON #-} @@ -1936,6 +1958,8 @@     {-# INLINE liftToEncoding #-}  instance (ToJSON v, ToJSONKey k) => ToJSON (H.HashMap k v) where+    {-# SPECIALIZE instance ToJSON Object #-}+     toJSON = toJSON1     {-# INLINE toJSON #-} @@ -2165,6 +2189,13 @@ -- tagged ------------------------------------------------------------------------------- +instance ToJSON1 Proxy where+    liftToJSON _ _ _ = Null+    {-# INLINE liftToJSON #-}++    liftToEncoding _ _ _ = E.null_+    {-# INLINE liftToEncoding #-}+ instance ToJSON (Proxy a) where     toJSON _ = Null     {-# INLINE toJSON #-}@@ -2173,6 +2204,13 @@     {-# INLINE toEncoding #-}  +instance ToJSON2 Tagged where+    liftToJSON2 _ _ t _ (Tagged x) = t x+    {-# INLINE liftToJSON2 #-}++    liftToEncoding2 _ _ t _ (Tagged x) = t x+    {-# INLINE liftToEncoding2 #-}+ instance ToJSON1 (Tagged a) where     liftToJSON t _ (Tagged x) = t x     {-# INLINE liftToJSON #-}@@ -2187,7 +2225,6 @@     toEncoding = toEncoding1     {-# INLINE toEncoding #-} - instance ToJSONKey b => ToJSONKey (Tagged a b) where     toJSONKey = contramapToJSONKeyFunction unTagged toJSONKey     toJSONKeyList = contramapToJSONKeyFunction (fmap unTagged) toJSONKeyList@@ -2769,7 +2806,7 @@  -- | Pack the chunks of a lazy bytestring into a single strict bytestring. packChunks :: L.ByteString -> S.ByteString-packChunks lbs = do+packChunks lbs =     S.unsafeCreate (fromIntegral $ L.length lbs) (copyChunks lbs)   where     copyChunks !L.Empty                         !_pf = return ()
README.markdown view
@@ -15,9 +15,9 @@  * `git clone git://github.com/bos/aeson.git` -There's also a [Mercurial mirror](http://bitbucket.org/bos/aeson):+See what's changed in recent (and upcoming) releases: -* `hg clone http://bitbucket.org/bos/aeson`+* https://github.com/bos/aeson/blob/master/changelog.md  (You can create and contribute changes using either git or Mercurial.) 
aeson.cabal view
@@ -1,5 +1,5 @@ name:            aeson-version:         1.0.2.1+version:         1.1.0.0 license:         BSD3 license-file:    LICENSE category:        Text, Web, JSON@@ -117,6 +117,7 @@     time >= 1.1.1.4,     time-locale-compat >= 0.1.1 && < 0.2,     unordered-containers >= 0.2.5.0,+    uuid-types >= 1.0.3 && <1.1,     vector >= 0.8    if flag(bytestring-builder)@@ -162,10 +163,12 @@     DataFamilies.Encoders     DataFamilies.Types     Encoders+    ErrorMessages     Functions     Instances     Options     Properties+    SerializationFormatSpec     Types     UnitTests     UnitTests.NullaryConstructors@@ -182,7 +185,9 @@     base-orphans >= 0.5.3 && <0.6,     base16-bytestring,     containers,+    directory,     dlist,+    filepath,     generic-deriving >= 1.10 && < 1.12,     ghc-prim >= 0.2,     hashable >= 1.2.4.0,@@ -196,6 +201,7 @@     time,     time-locale-compat,     unordered-containers,+    uuid-types,     vector,     quickcheck-instances >=0.3.12 @@ -220,7 +226,3 @@ source-repository head   type:     git   location: git://github.com/bos/aeson.git--source-repository head-  type:     mercurial-  location: https://bitbucket.org/bos/aeson
benchmarks/aeson-benchmarks.cabal view
@@ -53,6 +53,7 @@     time,     transformers,     unordered-containers >= 0.2.3.0,+    uuid-types >= 1.0.3 && <1.1,     vector >= 0.7.1    if flag(bytestring-builder)
changelog.md view
@@ -1,5 +1,47 @@ For the latest version of this document, please see [https://github.com/bos/aeson/blob/master/changelog.md](https://github.com/bos/aeson/blob/master/changelog.md). +# 1.1.0.0++* The operators for parsing fields now have named aliases:+  -  `.:` => `parseField`+  -  `.:?` => `parseFieldMaybe`+  -  `.:!` => `parseFieldMaybe'`+  - These functions now also have variants with explicit parser functions: `explicitParseField`, `explicitParseFieldMaybe`, "explicitParseFieldMaybe'`+Thanks to Oleg Grenrus.++* `ToJSONKey (Identity a)` and `FromJSONKey (Identity a)` no longer require the unnecessary `FromJSON a` constraint. Thanks to Oleg Grenrus.++* Added `Data.Aeson.Encoding.pair'` which is a more general version of `Data.Aeson.Encoding.pair`. Thanks to Andrew Martin.++* `Day`s BCE are properly encoded and `+` is now a valid prefix for `Day`s CE. Thanks to Matt Parsons.++* Some commonly used ToJSON instances are now specialized in order to improve compile time. Thanks to Bartosz Nitka.+++[JSONTestSuite](https://github.com/nst/JSONTestSuite) cleanups, all+motivated by tighter RFC 7159 compliance:++* The parser now rejects numbers for which+  [the integer portion contains a leading zero](https://github.com/bos/aeson/commit/3fb7c155f2255482b1b9566ec5c1eaf9895d630e).+* The parser now rejects numbers for which+  [a decimal point is not followed by at least one digit](https://github.com/bos/aeson/commit/ecfca35a45286dbe2bbaf5f62354be393bc59b66),+* The parser now rejects documents that contain [whitespace outside the+  set {space, newline, carriage return, tab}](https://github.com/bos/aeson/commit/8ef622c2ad8d4a109884e17c2792238a2a320e44).++Over 90% of JSONTestSuite tests currently pass. The remainder can be+categorised as follows:++* The string parser is strict with Unicode compliance where the RFC+  leaves room for implementation-defined behaviour (tests prefixed+  with "`i_string_`". (This is necessary because the `text` library+  cannot accommodate invalid Unicode.)++* The parser does not (and will not) support UTF-16, UTF-32, or byte+  order marks (BOM).+* The parser accepts unescaped control characters, even though the RFC+  states that control characters must be escaped. (This may change at+  some point, but doesn't seem important.)+ #### 1.0.2.1  * Fixes a regression where a bunch of valid characters caused an
+ tests/ErrorMessages.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module ErrorMessages+  (+    tests+  ) where++import Prelude ()+import Prelude.Compat++import Data.Aeson (FromJSON(..), eitherDecode)+import Data.Proxy (Proxy(..))+import Instances ()+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (Assertion, assertFailure, assertEqual)+import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.HashMap.Strict as HM++tests :: [Test]+tests =+    [+      testCase "Int" int+    , testCase "String" string+    , testCase "HashMap" hashMap+    ]++int :: Assertion+int = do+  let t = test (Proxy :: Proxy Int)+  t "\"\"" $ expected "Int" "String"+  t "[]" $ expected "Int" "Array"+  t "{}" $ expected "Int" "Object"+  t "null" $ expected "Int" "Null"++string :: Assertion+string = do+  let t = test (Proxy :: Proxy String)+  t "1" $ expected "String" "Number"+  t "[]" $ expected "String" "Array"+  t "{}" $ expected "String" "Object"+  t "null" $ expected "String" "Null"++hashMap :: Assertion+hashMap = do+  let t = test (Proxy :: Proxy (HM.HashMap String Int))+  t "\"\"" $ expected "HashMap k v" "String"+  t "[]" $ expected "HashMap k v" "Array"++expected :: String -> String -> String+expected ex enc = "Error in $: expected " ++ ex ++ ", encountered " ++ enc++test :: forall a proxy . (FromJSON a, Show a) => proxy a -> L.ByteString -> String -> Assertion+test _ v msg = case eitherDecode v of+    Left e -> assertEqual "Invalid error message" msg e+    Right (x :: a) -> assertFailure $ "Expected parsing to fail but it suceeded with: " ++ show x
tests/Instances.hs view
@@ -25,6 +25,7 @@ import Types import qualified Data.DList as DList import qualified Data.HashMap.Strict as HM+import qualified Data.UUID.Types as UUID  #if !MIN_VERSION_QuickCheck(2,9,0) import Control.Applicative (Const(..))@@ -218,3 +219,10 @@ instance Arbitrary a => Arbitrary (Identity a) where     arbitrary = Identity <$> arbitrary #endif++instance Arbitrary UUID.UUID where+    arbitrary = UUID.fromWords+        <$> arbitrary+        <*> arbitrary+        <*> arbitrary+        <*> arbitrary
tests/Properties.hs view
@@ -41,6 +41,7 @@ import qualified Data.Map as Map import qualified Data.Text as T import qualified Data.Text.Lazy as LT+import qualified Data.UUID.Types as UUID import qualified Data.Vector as V  encodeDouble :: Double -> Double -> Property@@ -202,6 +203,7 @@     , testProperty "Lazy Text" $ roundTripEq LT.empty     , testProperty "Foo" $ roundTripEq (undefined :: Foo)     , testProperty "Day" $ roundTripEq (undefined :: Day)+    , testProperty "BCE Day" $ roundTripEq (undefined :: BCEDay)     , testProperty "DotNetTime" $ roundTripEq (undefined :: Approx DotNetTime)     , testProperty "LocalTime" $ roundTripEq (undefined :: LocalTime)     , testProperty "TimeOfDay" $ roundTripEq (undefined :: TimeOfDay)@@ -217,6 +219,7 @@     , testProperty "Seq" $ roundTripEq (undefined :: Seq Int)     , testProperty "Rational" $ roundTripEq (undefined :: Rational)     , testProperty "Ratio Int" $ roundTripEq (undefined :: Ratio Int)+    , testProperty "UUID" $ roundTripEq UUID.nil     , testGroup "functors"       [ testProperty "Identity Char" $ roundTripEq (undefined :: I Int) @@ -278,6 +281,7 @@ #endif     , testProperty "Version" $ roundTripKey (undefined :: Version)     , testProperty "Lazy Text" $ roundTripKey (undefined :: LT.Text)+    , testProperty "UUID" $ roundTripKey UUID.nil     ]   , testGroup "toFromJSON" [       testProperty "Integer" (toFromJSON :: Integer -> Property)
+ tests/SerializationFormatSpec.hs view
@@ -0,0 +1,245 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+#if __GLASGOW_HASKELL__ >= 708+{-# LANGUAGE DataKinds #-}+#endif++------------------------------------------------------------------------------+-- These tests assert that the JSON serialization doesn't change by accident.+-----------------------------------------------------------------------------++module SerializationFormatSpec+  (+    tests+  ) where++import Prelude ()+import Prelude.Compat++import Control.Applicative (Const(..))+import Data.Aeson (FromJSON(..), decode, encode, genericParseJSON, genericToEncoding, genericToJSON)+import Data.Aeson.Types (Options(..), SumEncoding(..), ToJSON(..), defaultOptions)+import Data.Fixed (Pico)+import Data.Functor.Compose (Compose(..))+import Data.Functor.Identity (Identity(..))+import Data.Functor.Product (Product(..))+import Data.Functor.Sum (Sum(..))+import Data.List.NonEmpty (NonEmpty(..))+import Data.Proxy (Proxy(..))+import Data.Scientific (Scientific)+import Data.Tagged (Tagged(..))+import Data.Time (fromGregorian)+import Data.Word (Word8)+import GHC.Generics (Generic)+import Instances ()+import Test.Framework (Test, testGroup)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (assertFailure, assertEqual)+import Types (Approx(..), Compose3, Compose3', I)+import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.DList as DList+import qualified Data.HashMap.Strict as HM+import qualified Data.HashSet as HashSet+import qualified Data.IntMap as IntMap+import qualified Data.IntSet as IntSet+import qualified Data.Map as M+import qualified Data.Monoid as Monoid+import qualified Data.Semigroup as Semigroup+import qualified Data.Sequence as Seq+import qualified Data.Set as Set+import qualified Data.Tree as Tree+import qualified Data.UUID.Types as UUID+import qualified Data.Vector as Vector++tests :: [Test]+tests =+  [+    testGroup "To JSON representation" $ fmap assertJsonEncodingExample jsonEncodingExamples+  , testGroup "From JSON representation" $ fmap assertJsonExample jsonDecodingExamples+  , testGroup "To/From JSON representation" $ fmap assertJsonExample jsonExamples++  ]++jsonExamples :: [Example]+jsonExamples =+  [+    Example "Either Left" "{\"Left\":1}"  (Left 1 :: Either Int Int)+  , Example "Either Right" "{\"Right\":1}"  (Right 1 :: Either Int Int)+  , Example "Nothing"  "null"  (Nothing :: Maybe Int)+  , Example "Just"  "1"  (Just 1 :: Maybe Int)+  , Example "Proxy Int" "null"  (Proxy :: Proxy Int)+  , Example "Tagged Char Int" "1"  (Tagged 1 :: Tagged Char Int)+#if __GLASGOW_HASKELL__ >= 708+    -- Test Tagged instance is polykinded+  , Example "Tagged 123 Int" "1"  (Tagged 1 :: Tagged 123 Int)+#endif+  , Example "Const Char Int" "\"c\""  (Const 'c' :: Const Char Int)+  , Example "Tuple" "[1,2]"  ((1, 2) :: (Int, Int))+  , Example "NonEmpty" "[1,2,3]"  (1 :| [2, 3] :: NonEmpty Int)+  , Example "Seq" "[1,2,3]"  (Seq.fromList [1, 2, 3] ::  Seq.Seq Int)+  , Example "DList" "[1,2,3]"  (DList.fromList [1, 2, 3] :: DList.DList Int)+  , Example "()" "[]"  ()++  , Example "HashMap Int Int"          "{\"0\":1,\"2\":3}"  (HM.fromList [(0,1),(2,3)] :: HM.HashMap Int Int)+  , Example "Map Int Int"              "{\"0\":1,\"2\":3}"  (M.fromList [(0,1),(2,3)] :: M.Map Int Int)+  , Example "Map (Tagged Int Int) Int" "{\"0\":1,\"2\":3}"  (M.fromList [(Tagged 0,1),(Tagged 2,3)] :: M.Map (Tagged Int Int) Int)+  , Example "Map [Int] Int"            "[[[0],1],[[2],3]]"  (M.fromList [([0],1),([2],3)] :: M.Map [Int] Int)+  , Example "Map [Char] Int"           "{\"ab\":1,\"cd\":3}"  (M.fromList [("ab",1),("cd",3)] :: M.Map [Char] Int)+  , Example "Map [I Char] Int"         "{\"ab\":1,\"cd\":3}"  (M.fromList [(map pure "ab",1),(map pure "cd",3)] :: M.Map [I Char] Int)++  , Example "nan :: Double" "null"  (Approx $ 0/0 :: Approx Double)++  , Example "Ordering LT" "\"LT\"" LT+  , Example "Ordering EQ" "\"EQ\"" EQ+  , Example "Ordering GT" "\"GT\"" GT++  , Example "Float" "3.14" (3.14 :: Float)+  , Example "Pico" "3.14" (3.14 :: Pico)+  , Example "Scientific" "3.14" (3.14 :: Scientific)++  , Example "UUID" "\"c2cc10e1-57d6-4b6f-9899-38d972112d8c\"" $ UUID.fromWords+      0xc2cc10e1 0x57d64b6f 0x989938d9 0x72112d8c++  , Example "Set Int" "[1,2,3]" (Set.fromList [3, 2, 1] :: Set.Set Int)+  , Example "IntSet"  "[1,2,3]" (IntSet.fromList [3, 2, 1])+  , Example "IntMap" "[[1,2],[3,4]]" (IntMap.fromList [(3,4), (1,2)] :: IntMap.IntMap Int)+  , Example "Vector" "[1,2,3]" (Vector.fromList [1, 2, 3] :: Vector.Vector Int)+  , Example "HashSet Int" "[1,2,3]" (HashSet.fromList [3, 2, 1] :: HashSet.HashSet Int)+  , Example "Tree Int" "[1,[[2,[[3,[]],[4,[]]]],[5,[]]]]" (let n = Tree.Node in n 1 [n 2 [n 3 [], n 4 []], n 5 []] :: Tree.Tree Int)++  -- Three separate cases, as ordering in HashMap is not defined+  , Example "HashMap Float Int, NaN" "{\"NaN\":1}"  (Approx $ HM.singleton (0/0) 1 :: Approx (HM.HashMap Float Int))+  , Example "HashMap Float Int, Infinity" "{\"Infinity\":1}"  (HM.singleton (1/0) 1 :: HM.HashMap Float Int)+  , Example "HashMap Float Int, +Infinity" "{\"-Infinity\":1}"  (HM.singleton (negate 1/0) 1 :: HM.HashMap Float Int)++  -- Functors+  , Example "Identity Int" "1"  (pure 1 :: Identity Int)++  , Example "Identity Char" "\"x\""      (pure 'x' :: Identity Char)+  , Example "Identity String" "\"foo\""  (pure "foo" :: Identity String)+  , Example "[Identity Char]" "\"xy\""   ([pure 'x', pure 'y'] :: [Identity Char])++  , Example "Maybe Char" "\"x\""              (pure 'x' :: Maybe Char)+  , Example "Maybe String" "\"foo\""          (pure "foo" :: Maybe String)+  , Example "Maybe [Identity Char]" "\"xy\""  (pure [pure 'x', pure 'y'] :: Maybe [Identity Char])++  , 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 "Product I Maybe Int" "[1,2]"         (Pair (pure 1) (pure 2) :: Product I Maybe Int)+  , Example "Product I Maybe Int" "[1,null]"      (Pair (pure 1) Nothing :: Product I Maybe Int)+  , Example "Product I [] Char" "[\"a\",\"foo\"]" (Pair (pure 'a') "foo" :: Product I [] Char)++  , Example "Sum I [] Int: InL"  "{\"InL\":1}"       (InL (pure 1) :: Sum I [] Int)+  , Example "Sum I [] Int: InR"  "{\"InR\":[1,2]}"   (InR [1, 2] :: Sum I [] Int)+  , Example "Sum I [] Char: InR" "{\"InR\":\"foo\"}" (InR "foo" :: Sum I [] Char)++  , Example "Compose I  I  Int" "1"      (pure 1 :: Compose I I   Int)+  , Example "Compose I  [] Int" "[1]"    (pure 1 :: Compose I []  Int)+  , Example "Compose [] I  Int" "[1]"    (pure 1 :: Compose [] I  Int)+  , Example "Compose [] [] Int" "[[1]]"  (pure 1 :: Compose [] [] Int)++  , Example "Compose I  I  Char" "\"x\""    (pure 'x' :: Compose I  I  Char)+  , Example "Compose I  [] Char" "\"x\""    (pure 'x' :: Compose I  [] Char)+  , Example "Compose [] I  Char" "\"x\""    (pure 'x' :: Compose [] I  Char)+  , Example "Compose [] [] Char" "[\"x\"]"  (pure 'x' :: Compose [] [] Char)++  , Example "Compose3 I  I  I  Char" "\"x\""      (pure 'x' :: Compose3 I  I  I  Char)+  , Example "Compose3 I  I  [] Char" "\"x\""      (pure 'x' :: Compose3 I  I  [] Char)+  , Example "Compose3 I  [] I  Char" "\"x\""      (pure 'x' :: Compose3 I  [] I  Char)+  , Example "Compose3 I  [] [] Char" "[\"x\"]"    (pure 'x' :: Compose3 I  [] [] Char)+  , Example "Compose3 [] I  I  Char" "\"x\""      (pure 'x' :: Compose3 [] I  I  Char)+  , Example "Compose3 [] I  [] Char" "[\"x\"]"    (pure 'x' :: Compose3 [] I  [] Char)+  , Example "Compose3 [] [] I  Char" "[\"x\"]"    (pure 'x' :: Compose3 [] [] I  Char)+  , Example "Compose3 [] [] [] Char" "[[\"x\"]]"  (pure 'x' :: Compose3 [] [] [] Char)++  , Example "Compose3' I  I  I  Char" "\"x\""      (pure 'x' :: Compose3' I  I  I  Char)+  , Example "Compose3' I  I  [] Char" "\"x\""      (pure 'x' :: Compose3' I  I  [] Char)+  , Example "Compose3' I  [] I  Char" "\"x\""      (pure 'x' :: Compose3' I  [] I  Char)+  , Example "Compose3' I  [] [] Char" "[\"x\"]"    (pure 'x' :: Compose3' I  [] [] Char)+  , Example "Compose3' [] I  I  Char" "\"x\""      (pure 'x' :: Compose3' [] I  I  Char)+  , Example "Compose3' [] I  [] Char" "[\"x\"]"    (pure 'x' :: Compose3' [] I  [] Char)+  , Example "Compose3' [] [] I  Char" "[\"x\"]"    (pure 'x' :: Compose3' [] [] I  Char)+  , Example "Compose3' [] [] [] Char" "[[\"x\"]]"  (pure 'x' :: Compose3' [] [] [] Char)++  , Example "MyEither Int String: Left"  "42"      (MyLeft 42     :: MyEither Int String)+  , Example "MyEither Int String: Right" "\"foo\"" (MyRight "foo" :: MyEither Int String)++  -- newtypes from Monoid/Semigroup+  , Example "Monoid.Dual Int" "2" (pure 2 :: Monoid.Dual Int)+  , Example "Monoid.First Int" "2" (pure 2 :: Monoid.First Int)+  , Example "Monoid.Last Int" "2" (pure 2 :: Monoid.Last Int)+  , Example "Semigroup.Min Int" "2" (pure 2 :: Semigroup.Min Int)+  , Example "Semigroup.Max Int" "2" (pure 2 :: Semigroup.Max Int)+  , Example "Semigroup.First Int" "2" (pure 2 :: Semigroup.First Int)+  , Example "Semigroup.Last Int" "2" (pure 2 :: Semigroup.Last Int)+  , Example "Semigroup.WrappedMonoid Int" "2" (Semigroup.WrapMonoid 2 :: Semigroup.WrappedMonoid Int)+  , Example "Semigroup.Option Just" "2" (pure 2 :: Semigroup.Option Int)+  , Example "Semigroup.Option Nothing" "null" (Semigroup.Option (Nothing :: Maybe Bool))+  ]++jsonEncodingExamples :: [Example]+jsonEncodingExamples =+  [+  -- Maybe serialising is lossy+  -- https://github.com/bos/aeson/issues/376+    Example "Just Nothing" "null" (Just Nothing :: Maybe (Maybe Int))+  -- infinities cannot be recovered, null is decoded as NaN+  , Example "inf :: Double" "null" (Approx $ 1/0 :: Approx Double)+  ]++jsonDecodingExamples :: [Example]+jsonDecodingExamples = [+  -- Maybe serialising is lossy+  -- https://github.com/bos/aeson/issues/376+    MaybeExample "Nothing"      "null" (Just $ Nothing :: Maybe (Maybe Int))+  , MaybeExample "Just"         "1"    (Just $ Just 1 :: Maybe (Maybe Int))+  , MaybeExample "Just Nothing" "null" (Just $ Nothing :: Maybe (Maybe (Maybe Int)))+  -- Integral values are truncated, and overflowed+  -- https://github.com/bos/aeson/issues/317+  , MaybeExample "Word8 3"    "3"    (Just 3 :: Maybe Word8)+  , MaybeExample "Word8 3.00" "3.00" (Just 3 :: Maybe Word8)+  , MaybeExample "Word8 3.14" "3.14" (Nothing :: Maybe Word8)+  , MaybeExample "Word8 -1"   "-1"   (Nothing :: Maybe Word8)+  , 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))+  ]++data Example where+    Example+        :: (Eq a, Show a, ToJSON a, FromJSON a)+        => String -> L.ByteString -> a -> Example+    MaybeExample+        :: (Eq a, Show a, FromJSON a)+        => String -> L.ByteString -> Maybe a -> Example++data MyEither a b = MyLeft a | MyRight b+  deriving (Generic, Show, Eq)++instance (ToJSON a, ToJSON b) => ToJSON (MyEither a b) where+    toJSON = genericToJSON defaultOptions { sumEncoding = UntaggedValue }+    toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }++instance (FromJSON a, FromJSON b) => FromJSON (MyEither a b) where+    parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }++assertJsonExample :: Example -> Test+assertJsonExample (Example name bs val) = testCase name $ do+    assertEqual "encode"           bs         (encode val)+    assertEqual "encode/via value" bs         (encode $ toJSON val)+    assertEqual "decode"           (Just val) (decode bs)+assertJsonExample (MaybeExample name bs mval) = testCase name $+    assertEqual "decode" mval (decode bs)++assertJsonEncodingExample :: Example -> Test+assertJsonEncodingExample (Example name bs val) = testCase name $ do+    assertEqual "encode"           bs (encode val)+    assertEqual "encode/via value" bs (encode $ toJSON val)+assertJsonEncodingExample (MaybeExample name _ _) = testCase name $+    assertFailure "cannot encode MaybeExample"
tests/Types.hs view
@@ -15,9 +15,11 @@ import Data.Functor.Compose (Compose (..)) import Data.Functor.Identity (Identity (..)) import Data.Text+import Data.Time (Day (..), fromGregorian) import GHC.Generics-import Test.QuickCheck (Property, counterexample)+import Test.QuickCheck (Arbitrary (..), Property, counterexample) import qualified Data.Map as Map+import Data.Aeson  type I = Identity type Compose3  f g h = Compose (Compose f g) h@@ -115,3 +117,19 @@ failure :: Show a => String -> String -> a -> Property failure func msg v = counterexample                      (func ++ " failed: " ++ msg ++ ", " ++ show v) False++newtype BCEDay = BCEDay Day+  deriving (Eq, Show)++zeroDay :: Day+zeroDay = fromGregorian 0 0 0++instance Arbitrary BCEDay where+    arbitrary = fmap (BCEDay . ModifiedJulianDay . (+ toModifiedJulianDay zeroDay)) arbitrary++instance ToJSON BCEDay where+    toJSON (BCEDay d)     = toJSON d+    toEncoding (BCEDay d) = toEncoding d++instance FromJSON BCEDay where+    parseJSON = fmap BCEDay . parseJSON
tests/UnitTests.hs view
@@ -1,14 +1,11 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-}-#if __GLASGOW_HASKELL__ >= 708-{-# LANGUAGE DataKinds #-}-#endif +-- For Data.Aeson.Types.camelTo {-# OPTIONS_GHC -fno-warn-deprecations #-}  module UnitTests@@ -20,61 +17,49 @@ import Prelude () import Prelude.Compat -import Control.Applicative (Const(..))-import Control.Monad (forM, forM_)-import Data.Aeson ((.=), (.:), (.:?), (.:!), FromJSON(..), FromJSONKeyFunction(..), FromJSONKey(..), ToJSON1(..), decode, eitherDecode, encode, genericParseJSON, genericToEncoding, genericToJSON, object, withObject)+import Control.Monad (forM, forM_, unless)+import Data.Aeson ((.=), (.:), (.:?), (.:!), FromJSON(..), FromJSONKeyFunction(..), FromJSONKey(..), ToJSON1(..), decode, eitherDecode, encode, genericToEncoding, genericToJSON, object, withObject) import Data.Aeson.Internal (JSONPathElement(..), formatError) import Data.Aeson.TH (deriveJSON, deriveToJSON, deriveToJSON1) import Data.Aeson.Text (encodeToTextBuilder)-import Data.Aeson.Types (Options(..), SumEncoding(..), ToJSON(..), Value, camelTo, camelTo2, defaultOptions, omitNothingFields)+import Data.Aeson.Types (Options(..), ToJSON(..), Value, camelTo, camelTo2, defaultOptions, omitNothingFields) import Data.Char (toUpper)-import Data.Fixed (Pico)-import Data.Functor.Compose (Compose(..))-import Data.Functor.Identity (Identity(..))-import Data.Functor.Product (Product(..))-import Data.Functor.Sum (Sum(..))+import Data.Either.Compat (isLeft, isRight) import Data.Hashable (hash)-import Data.List.NonEmpty (NonEmpty(..))+import Data.List (sort) import Data.Maybe (fromMaybe)-import Data.Proxy (Proxy(..))-import Data.Scientific (Scientific) import Data.Sequence (Seq) import Data.Tagged (Tagged(..)) import Data.Text (Text) import Data.Time (UTCTime) import Data.Time.Format (parseTime) import Data.Time.Locale.Compat (defaultTimeLocale)-import Data.Word (Word8) import GHC.Generics (Generic) import Instances ()+import System.Directory (doesDirectoryExist, getDirectoryContents)+import System.Exit (exitWith, ExitCode(ExitFailure))+import System.FilePath ((</>), takeExtension, takeFileName)+import System.IO (hPutStrLn, stderr) import Test.Framework (Test, testGroup) import Test.Framework.Providers.HUnit (testCase)-import Test.HUnit (Assertion, assertFailure, assertEqual)+import Test.HUnit (Assertion, assertBool, assertFailure, assertEqual) import Text.Printf (printf)-import Types (Approx(..), Compose3, Compose3', I) import UnitTests.NullaryConstructors (nullaryConstructors) import qualified Data.ByteString.Base16.Lazy as LBase16 import qualified Data.ByteString.Lazy.Char8 as L-import qualified Data.DList as DList-import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HashSet-import qualified Data.IntMap as IntMap-import qualified Data.IntSet as IntSet-import qualified Data.Map as M-import qualified Data.Monoid as Monoid-import qualified Data.Semigroup as Semigroup-import qualified Data.Sequence as Seq-import qualified Data.Set as Set import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy.Builder as TLB import qualified Data.Text.Lazy.Encoding as LT import qualified Data.Text.Lazy.Encoding as TLE-import qualified Data.Tree as Tree-import qualified Data.Vector as Vector+import qualified ErrorMessages+import qualified SerializationFormatSpec  tests :: Test tests = testGroup "unit" [-    testGroup "camelCase" [+    testGroup "SerializationFormatSpec" SerializationFormatSpec.tests+  , testGroup "ErrorMessages" ErrorMessages.tests+  , testGroup "camelCase" [       testCase "camelTo" $ roundTripCamel "aName"     , testCase "camelTo" $ roundTripCamel "another"     , testCase "camelTo" $ roundTripCamel "someOtherName"@@ -97,9 +82,6 @@       testCase "example 1" $ formatErrorExample     ]   , testGroup ".:, .:?, .:!" $ fmap (testCase "-") dotColonMark-  , testGroup "To JSON representation" $ fmap assertJsonEncodingExample jsonEncodingExamples-  , testGroup "From JSON representation" $ fmap assertJsonExample jsonDecodingExamples-  , testGroup "To/From JSON representation" $ fmap assertJsonExample jsonExamples   , testGroup "JSONPath" $ fmap (testCase "-") jsonPath   , testGroup "Hashable laws" $ fmap (testCase "-") hashableLaws   , testGroup "Issue #351" $ fmap (testCase "-") issue351@@ -123,17 +105,6 @@     capitalize t = toUpper (head t) : tail t  -data MyEither a b = MyLeft a | MyRight b-  deriving (Generic, Show, Eq)--instance (ToJSON a, ToJSON b) => ToJSON (MyEither a b) where-    toJSON = genericToJSON defaultOptions { sumEncoding = UntaggedValue }-    toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }--instance (FromJSON a, FromJSON b) => FromJSON (MyEither a b) where-    parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }-- data Wibble = Wibble {     wibbleString :: String   , wibbleInt :: Int@@ -274,173 +245,6 @@         ex3 = "{\"value\": null }"  --------------------------------------------------------------------------------- These tests assert that the JSON serialization doesn't change by accident.--------------------------------------------------------------------------------data Example where-    Example-        :: (Eq a, Show a, ToJSON a, FromJSON a)-        => String -> L.ByteString -> a -> Example-    MaybeExample-        :: (Eq a, Show a, FromJSON a)-        => String -> L.ByteString -> Maybe a -> Example--assertJsonExample :: Example -> Test-assertJsonExample (Example name bs val) = testCase name $ do-    assertEqual "encode"           bs         (encode val)-    assertEqual "encode/via value" bs         (encode $ toJSON val)-    assertEqual "decode"           (Just val) (decode bs)-assertJsonExample (MaybeExample name bs mval) = testCase name $-    assertEqual "decode" mval (decode bs)--assertJsonEncodingExample :: Example -> Test-assertJsonEncodingExample (Example name bs val) = testCase name $ do-    assertEqual "encode"           bs (encode val)-    assertEqual "encode/via value" bs (encode $ toJSON val)-assertJsonEncodingExample (MaybeExample name _ _) = testCase name $-    assertFailure "cannot encode MaybeExample"--jsonEncodingExamples :: [Example]-jsonEncodingExamples =-  [-  -- Maybe serialising is lossy-  -- https://github.com/bos/aeson/issues/376-    Example "Just Nothing" "null" (Just Nothing :: Maybe (Maybe Int))-  -- infinities cannot be recovered, null is decoded as NaN-  , Example "inf :: Double" "null" (Approx $ 1/0 :: Approx Double)-  ]--jsonDecodingExamples :: [Example]-jsonDecodingExamples = [-  -- Maybe serialising is lossy-  -- https://github.com/bos/aeson/issues/376-    MaybeExample "Nothing"      "null" (Just $ Nothing :: Maybe (Maybe Int))-  , MaybeExample "Just"         "1"    (Just $ Just 1 :: Maybe (Maybe Int))-  , MaybeExample "Just Nothing" "null" (Just $ Nothing :: Maybe (Maybe (Maybe Int)))-  -- Integral values are truncated, and overflowed-  -- https://github.com/bos/aeson/issues/317-  , MaybeExample "Word8 3"    "3"    (Just 3 :: Maybe Word8)-  , MaybeExample "Word8 3.00" "3.00" (Just 3 :: Maybe Word8)-  , MaybeExample "Word8 3.14" "3.14" (Nothing :: Maybe Word8)-  , MaybeExample "Word8 -1"   "-1"   (Nothing :: Maybe Word8)-  , MaybeExample "Word8 300"  "300"  (Nothing :: Maybe Word8)-  ]--jsonExamples :: [Example]-jsonExamples =-  [-    Example "Either Left" "{\"Left\":1}"  (Left 1 :: Either Int Int)-  , Example "Either Right" "{\"Right\":1}"  (Right 1 :: Either Int Int)-  , Example "Nothing"  "null"  (Nothing :: Maybe Int)-  , Example "Just"  "1"  (Just 1 :: Maybe Int)-  , Example "Proxy Int" "null"  (Proxy :: Proxy Int)-  , Example "Tagged Char Int" "1"  (Tagged 1 :: Tagged Char Int)-#if __GLASGOW_HASKELL__ >= 708-    -- Test Tagged instance is polykinded-  , Example "Tagged 123 Int" "1"  (Tagged 1 :: Tagged 123 Int)-#endif-  , Example "Const Char Int" "\"c\""  (Const 'c' :: Const Char Int)-  , Example "Tuple" "[1,2]"  ((1, 2) :: (Int, Int))-  , Example "NonEmpty" "[1,2,3]"  (1 :| [2, 3] :: NonEmpty Int)-  , Example "Seq" "[1,2,3]"  (Seq.fromList [1, 2, 3] ::  Seq.Seq Int)-  , Example "DList" "[1,2,3]"  (DList.fromList [1, 2, 3] :: DList.DList Int)-  , Example "()" "[]"  ()--  , Example "HashMap Int Int"          "{\"0\":1,\"2\":3}"  (HM.fromList [(0,1),(2,3)] :: HM.HashMap Int Int)-  , Example "Map Int Int"              "{\"0\":1,\"2\":3}"  (M.fromList [(0,1),(2,3)] :: M.Map Int Int)-  , Example "Map (Tagged Int Int) Int" "{\"0\":1,\"2\":3}"  (M.fromList [(Tagged 0,1),(Tagged 2,3)] :: M.Map (Tagged Int Int) Int)-  , Example "Map [Int] Int"            "[[[0],1],[[2],3]]"  (M.fromList [([0],1),([2],3)] :: M.Map [Int] Int)-  , Example "Map [Char] Int"           "{\"ab\":1,\"cd\":3}"  (M.fromList [("ab",1),("cd",3)] :: M.Map [Char] Int)-  , Example "Map [I Char] Int"         "{\"ab\":1,\"cd\":3}"  (M.fromList [(map pure "ab",1),(map pure "cd",3)] :: M.Map [I Char] Int)--  , Example "nan :: Double" "null"  (Approx $ 0/0 :: Approx Double)--  , Example "Ordering LT" "\"LT\"" LT-  , Example "Ordering EQ" "\"EQ\"" EQ-  , Example "Ordering GT" "\"GT\"" GT--  , Example "Float" "3.14" (3.14 :: Float)-  , Example "Pico" "3.14" (3.14 :: Pico)-  , Example "Scientific" "3.14" (3.14 :: Scientific)--  , Example "Set Int" "[1,2,3]" (Set.fromList [3, 2, 1] :: Set.Set Int)-  , Example "IntSet"  "[1,2,3]" (IntSet.fromList [3, 2, 1])-  , Example "IntMap" "[[1,2],[3,4]]" (IntMap.fromList [(3,4), (1,2)] :: IntMap.IntMap Int)-  , Example "Vector" "[1,2,3]" (Vector.fromList [1, 2, 3] :: Vector.Vector Int)-  , Example "HashSet Int" "[1,2,3]" (HashSet.fromList [3, 2, 1] :: HashSet.HashSet Int)-  , Example "Tree Int" "[1,[[2,[[3,[]],[4,[]]]],[5,[]]]]" (let n = Tree.Node in n 1 [n 2 [n 3 [], n 4 []], n 5 []] :: Tree.Tree Int)--  -- Three separate cases, as ordering in HashMap is not defined-  , Example "HashMap Float Int, NaN" "{\"NaN\":1}"  (Approx $ HM.singleton (0/0) 1 :: Approx (HM.HashMap Float Int))-  , Example "HashMap Float Int, Infinity" "{\"Infinity\":1}"  (HM.singleton (1/0) 1 :: HM.HashMap Float Int)-  , Example "HashMap Float Int, +Infinity" "{\"-Infinity\":1}"  (HM.singleton (negate 1/0) 1 :: HM.HashMap Float Int)--  -- Functors-  , Example "Identity Int" "1"  (pure 1 :: Identity Int)--  , Example "Identity Char" "\"x\""      (pure 'x' :: Identity Char)-  , Example "Identity String" "\"foo\""  (pure "foo" :: Identity String)-  , Example "[Identity Char]" "\"xy\""   ([pure 'x', pure 'y'] :: [Identity Char])--  , Example "Maybe Char" "\"x\""              (pure 'x' :: Maybe Char)-  , Example "Maybe String" "\"foo\""          (pure "foo" :: Maybe String)-  , Example "Maybe [Identity Char]" "\"xy\""  (pure [pure 'x', pure 'y'] :: Maybe [Identity Char])--  , Example "Product I Maybe Int" "[1,2]"         (Pair (pure 1) (pure 2) :: Product I Maybe Int)-  , Example "Product I Maybe Int" "[1,null]"      (Pair (pure 1) Nothing :: Product I Maybe Int)-  , Example "Product I [] Char" "[\"a\",\"foo\"]" (Pair (pure 'a') "foo" :: Product I [] Char)--  , Example "Sum I [] Int: InL"  "{\"InL\":1}"       (InL (pure 1) :: Sum I [] Int)-  , Example "Sum I [] Int: InR"  "{\"InR\":[1,2]}"   (InR [1, 2] :: Sum I [] Int)-  , Example "Sum I [] Char: InR" "{\"InR\":\"foo\"}" (InR "foo" :: Sum I [] Char)--  , Example "Compose I  I  Int" "1"      (pure 1 :: Compose I I   Int)-  , Example "Compose I  [] Int" "[1]"    (pure 1 :: Compose I []  Int)-  , Example "Compose [] I  Int" "[1]"    (pure 1 :: Compose [] I  Int)-  , Example "Compose [] [] Int" "[[1]]"  (pure 1 :: Compose [] [] Int)--  , Example "Compose I  I  Char" "\"x\""    (pure 'x' :: Compose I  I  Char)-  , Example "Compose I  [] Char" "\"x\""    (pure 'x' :: Compose I  [] Char)-  , Example "Compose [] I  Char" "\"x\""    (pure 'x' :: Compose [] I  Char)-  , Example "Compose [] [] Char" "[\"x\"]"  (pure 'x' :: Compose [] [] Char)--  , Example "Compose3 I  I  I  Char" "\"x\""      (pure 'x' :: Compose3 I  I  I  Char)-  , Example "Compose3 I  I  [] Char" "\"x\""      (pure 'x' :: Compose3 I  I  [] Char)-  , Example "Compose3 I  [] I  Char" "\"x\""      (pure 'x' :: Compose3 I  [] I  Char)-  , Example "Compose3 I  [] [] Char" "[\"x\"]"    (pure 'x' :: Compose3 I  [] [] Char)-  , Example "Compose3 [] I  I  Char" "\"x\""      (pure 'x' :: Compose3 [] I  I  Char)-  , Example "Compose3 [] I  [] Char" "[\"x\"]"    (pure 'x' :: Compose3 [] I  [] Char)-  , Example "Compose3 [] [] I  Char" "[\"x\"]"    (pure 'x' :: Compose3 [] [] I  Char)-  , Example "Compose3 [] [] [] Char" "[[\"x\"]]"  (pure 'x' :: Compose3 [] [] [] Char)--  , Example "Compose3' I  I  I  Char" "\"x\""      (pure 'x' :: Compose3' I  I  I  Char)-  , Example "Compose3' I  I  [] Char" "\"x\""      (pure 'x' :: Compose3' I  I  [] Char)-  , Example "Compose3' I  [] I  Char" "\"x\""      (pure 'x' :: Compose3' I  [] I  Char)-  , Example "Compose3' I  [] [] Char" "[\"x\"]"    (pure 'x' :: Compose3' I  [] [] Char)-  , Example "Compose3' [] I  I  Char" "\"x\""      (pure 'x' :: Compose3' [] I  I  Char)-  , Example "Compose3' [] I  [] Char" "[\"x\"]"    (pure 'x' :: Compose3' [] I  [] Char)-  , Example "Compose3' [] [] I  Char" "[\"x\"]"    (pure 'x' :: Compose3' [] [] I  Char)-  , Example "Compose3' [] [] [] Char" "[[\"x\"]]"  (pure 'x' :: Compose3' [] [] [] Char)--  , Example "MyEither Int String: Left"  "42"      (MyLeft 42     :: MyEither Int String)-  , Example "MyEither Int String: Right" "\"foo\"" (MyRight "foo" :: MyEither Int String)--  -- newtypes from Monoid/Semigroup-  , Example "Monoid.Dual Int" "2" (pure 2 :: Monoid.Dual Int)-  , Example "Monoid.First Int" "2" (pure 2 :: Monoid.First Int)-  , Example "Monoid.Last Int" "2" (pure 2 :: Monoid.Last Int)-  , Example "Semigroup.Min Int" "2" (pure 2 :: Semigroup.Min Int)-  , Example "Semigroup.Max Int" "2" (pure 2 :: Semigroup.Max Int)-  , Example "Semigroup.First Int" "2" (pure 2 :: Semigroup.First Int)-  , Example "Semigroup.Last Int" "2" (pure 2 :: Semigroup.Last Int)-  , Example "Semigroup.WrappedMonoid Int" "2" (Semigroup.WrapMonoid 2 :: Semigroup.WrappedMonoid Int)-  , Example "Semigroup.Option Just" "2" (pure 2 :: Semigroup.Option Int)-  , Example "Semigroup.Option Nothing" "null" (Semigroup.Option (Nothing :: Maybe Bool))-  ]---------------------------------------------------------------------------------- -- These tests check that JSONPath is tracked correctly ----------------------------------------------------------------------------- @@ -536,7 +340,8 @@ ioTests :: IO [Test] ioTests = do   enc <- encoderComparisonTests-  return [enc]+  js <- jsonTestSuite+  return [enc, js]  encoderComparisonTests :: IO Test encoderComparisonTests = do@@ -595,6 +400,82 @@       | L.length s == 8 =           L.concat ["\"\\u", L.take 4 s, "\\u", L.drop 4 s, "\""]       | otherwise = error "unescapeString: can't happen"++-- JSONTestSuite++jsonTestSuiteTest :: FilePath -> Test+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 Test+jsonTestSuite = do+  let suitePath = "tests/JSONTestSuite"+  exists <- doesDirectoryExist suitePath+  unless exists $ do+    hPutStrLn stderr $ "git clone https://github.com/nst/JSONTestSuite " +++                       suitePath+    exitWith (ExitFailure 1)+  -- ignore test_transform for now+  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"+  , "n_string_unescaped_crtl_char.json"+  , "n_string_unescaped_newline.json"+  , "n_string_unescaped_tab.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)