aeson 0.8.0.2 → 0.8.1.0
raw patch · 11 files changed
+301/−70 lines, 11 filesdep +transformersdep ~attoparsecdep ~bytestringdep ~unordered-containersPVP ok
version bump matches the API change (PVP)
Dependencies added: transformers
Dependency ranges changed: attoparsec, bytestring, unordered-containers
API changes (from Hackage documentation)
Files
- Data/Aeson/Encode.hs +6/−14
- Data/Aeson/Parser/Internal.hs +73/−22
- Data/Aeson/Types/Class.hs +4/−2
- Data/Aeson/Types/Instances.hs +44/−10
- Data/Aeson/Types/Internal.hs +1/−1
- aeson.cabal +5/−4
- benchmarks/aeson-benchmarks.cabal +65/−6
- benchmarks/json-data/sigma.json too large to diff
- changelog.md +26/−0
- examples/TemplateHaskell.hs +0/−4
- tests/Properties.hs +77/−7
Data/Aeson/Encode.hs view
@@ -29,10 +29,9 @@ import Data.Aeson.Types (Value(..)) import Data.Monoid (mappend)-import Data.Scientific (Scientific, coefficient, base10Exponent)+import Data.Scientific (FPFormat(..), Scientific, base10Exponent) import Data.Text.Lazy.Builder-import Data.Text.Lazy.Builder.Int (decimal)-import Data.Text.Lazy.Builder.Scientific (scientificBuilder)+import Data.Text.Lazy.Builder.Scientific (formatScientificBuilder) import Numeric (showHex) import qualified Data.HashMap.Strict as H import qualified Data.Text as T@@ -77,8 +76,6 @@ where (h,t) = {-# SCC "break" #-} T.break isEscape q isEscape c = c == '\"' || c == '\\' ||- c == '<' ||- c == '>' || c < '\x20' escape '\"' = "\\\"" escape '\\' = "\\\\"@@ -86,22 +83,17 @@ escape '\r' = "\\r" escape '\t' = "\\t" - -- The following prevents untrusted JSON strings containing </script> or -->- -- from causing an XSS vulnerability:- escape '<' = "\\u003c"- escape '>' = "\\u003e"- escape c | c < '\x20' = fromString $ "\\u" ++ replicate (4 - length h) '0' ++ h | otherwise = singleton c where h = showHex (fromEnum c) "" fromScientific :: Scientific -> Builder-fromScientific s- | e < 0 = scientificBuilder s- | otherwise = decimal (coefficient s * 10 ^ e)+fromScientific s = formatScientificBuilder format prec s where- e = base10Exponent s+ (format, prec)+ | base10Exponent s < 0 = (Generic, Nothing)+ | otherwise = (Fixed, Just 0) (<>) :: Builder -> Builder -> Builder (<>) = mappend
Data/Aeson/Parser/Internal.hs view
@@ -28,26 +28,31 @@ , eitherDecodeStrictWith ) where -import Data.ByteString.Builder- (Builder, byteString, toLazyByteString, charUtf8, word8)- import Control.Applicative ((*>), (<$>), (<*), liftA2, pure)+import Control.Monad.IO.Class (liftIO) import Data.Aeson.Types (Result(..), Value(..)) import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, scientific, skipSpace, string) import Data.Bits ((.|.), shiftL)-import Data.ByteString (ByteString)+import Data.ByteString.Internal (ByteString(..)) import Data.Char (chr)-import Data.Monoid (mappend, mempty) import Data.Text (Text) import Data.Text.Encoding (decodeUtf8')+import Data.Text.Internal.Encoding.Utf8 (ord2, ord3, ord4)+import Data.Text.Internal.Unsafe.Char (ord) import Data.Vector as Vector (Vector, fromList) import Data.Word (Word8)+import Foreign.ForeignPtr (withForeignPtr)+import Foreign.Ptr (minusPtr)+import Foreign.Ptr (Ptr, plusPtr)+import Foreign.Storable (poke)+import System.IO.Unsafe (unsafePerformIO) import qualified Data.Attoparsec.ByteString as A import qualified Data.Attoparsec.Lazy as L import qualified Data.Attoparsec.Zepto as Z import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Internal as B import qualified Data.ByteString.Unsafe as B import qualified Data.HashMap.Strict as H @@ -109,7 +114,7 @@ objectValues :: Parser Text -> Parser Value -> Parser (H.HashMap Text Value) objectValues str val = do skipSpace- let pair = liftA2 (,) (str <* skipSpace) (char ':' *> skipSpace *> val)+ let pair = liftA2 (,) (str <* skipSpace) (char ':' *> val) H.fromList <$> commaSeparated pair CLOSE_CURLY {-# INLINE objectValues #-} @@ -154,6 +159,7 @@ -- to preserve interoperability and security. value :: Parser Value value = do+ skipSpace w <- A.peekWord8' case w of DOUBLE_QUOTE -> A.anyWord8 *> (String <$> jstring_)@@ -169,6 +175,7 @@ -- | Strict version of 'value'. See also 'json''. value' :: Parser Value value' = do+ skipSpace w <- A.peekWord8' case w of DOUBLE_QUOTE -> do@@ -198,7 +205,7 @@ else Just (c == BACKSLASH) _ <- A.word8 DOUBLE_QUOTE s1 <- if BACKSLASH `B.elem` s- then case Z.parse unescape s of+ then case unescape s of Right r -> return r Left err -> fail err else return s@@ -209,42 +216,55 @@ {-# INLINE jstring_ #-} -unescape :: Z.Parser ByteString-unescape = toByteString <$> go mempty where- go acc = do+unescape :: ByteString -> Either String ByteString+unescape s = unsafePerformIO $ do+ let len = B.length s+ fp <- B.mallocByteString len+ -- We perform no bounds checking when writing to the destination+ -- string, as unescaping always makes it shorter than the source.+ withForeignPtr fp $ \ptr -> do+ ret <- Z.parseT (go ptr) s+ case ret of+ Left err -> return (Left err)+ Right p -> do+ let newlen = p `minusPtr` ptr+ slop = len - newlen+ Right <$> if slop >= 128 && slop >= len `quot` 4+ then B.create newlen $ \np -> B.memcpy np ptr newlen+ else return (PS fp 0 newlen)+ where+ go ptr = do h <- Z.takeWhile (/=BACKSLASH) let rest = do start <- Z.take 2 let !slash = B.unsafeHead start !t = B.unsafeIndex start 1- escape = case B.findIndex (==t) "\"\\/ntbrfu" of+ escape = case B.elemIndex t "\"\\/ntbrfu" of Just i -> i _ -> 255 if slash /= BACKSLASH || escape == 255 then fail "invalid JSON escape sequence"- else do- let cont m = go (acc `mappend` byteString h `mappend` m)- {-# INLINE cont #-}+ else if t /= 117 -- 'u'- then cont (word8 (B.unsafeIndex mapping escape))+ then copy h ptr >>= word8 (B.unsafeIndex mapping escape) >>= go else do a <- hexQuad if a < 0xd800 || a > 0xdfff- then cont (charUtf8 (chr a))+ then copy h ptr >>= charUtf8 (chr a) >>= go else do b <- Z.string "\\u" *> hexQuad if a <= 0xdbff && b >= 0xdc00 && b <= 0xdfff then let !c = ((a - 0xd800) `shiftL` 10) + (b - 0xdc00) + 0x10000- in cont (charUtf8 (chr c))+ in copy h ptr >>= charUtf8 (chr c) >>= go else fail "invalid UTF-16 surrogates" done <- Z.atEnd if done- then return (acc `mappend` byteString h)+ then copy h ptr else rest mapping = "\"\\/\n\t\b\r\f" -hexQuad :: Z.Parser Int+hexQuad :: Z.ZeptoT IO Int hexQuad = do s <- Z.take 4 let hex n | w >= C_0 && w <= C_9 = w - C_0@@ -321,6 +341,37 @@ jsonEOF' :: Parser Value jsonEOF' = json' <* skipSpace <* endOfInput -toByteString :: Builder -> ByteString-toByteString = L.toStrict . toLazyByteString-{-# INLINE toByteString #-}+word8 :: Word8 -> Ptr Word8 -> Z.ZeptoT IO (Ptr Word8)+word8 w ptr = do+ liftIO $ poke ptr w+ return $! ptr `plusPtr` 1++copy :: ByteString -> Ptr Word8 -> Z.ZeptoT IO (Ptr Word8)+copy (PS fp off len) ptr =+ liftIO . withForeignPtr fp $ \src -> do+ B.memcpy ptr (src `plusPtr` off) len+ return $! ptr `plusPtr` len++charUtf8 :: Char -> Ptr Word8 -> Z.ZeptoT IO (Ptr Word8)+charUtf8 ch ptr+ | ch < '\x80' = liftIO $ do+ poke ptr (fromIntegral (ord ch))+ return $! ptr `plusPtr` 1+ | ch < '\x800' = liftIO $ do+ let (a,b) = ord2 ch+ poke ptr a+ poke (ptr `plusPtr` 1) b+ return $! ptr `plusPtr` 2+ | ch < '\xffff' = liftIO $ do+ let (a,b,c) = ord3 ch+ poke ptr a+ poke (ptr `plusPtr` 1) b+ poke (ptr `plusPtr` 2) c+ return $! ptr `plusPtr` 3+ | otherwise = liftIO $ do+ let (a,b,c,d) = ord4 ch+ poke ptr a+ poke (ptr `plusPtr` 1) b+ poke (ptr `plusPtr` 2) c+ poke (ptr `plusPtr` 3) d+ return $! ptr `plusPtr` 4
Data/Aeson/Types/Class.hs view
@@ -129,7 +129,8 @@ -- -- An example type and instance: ----- @{-\# LANGUAGE OverloadedStrings #-}+-- @+-- {-\# LANGUAGE OverloadedStrings #-} -- -- data Coord = Coord { x :: Double, y :: Double } --@@ -165,7 +166,8 @@ -- -- For example the previous example can be simplified to just: ----- @{-\# LANGUAGE DeriveGeneric \#-}+-- @+-- {-\# LANGUAGE DeriveGeneric \#-} -- -- import GHC.Generics --
Data/Aeson/Types/Instances.hs view
@@ -64,10 +64,12 @@ import qualified Data.Scientific as Scientific (coefficient, base10Exponent, fromFloatDigits, toRealFloat) import Data.Attoparsec.Number (Number(..)) import Data.Fixed+import Data.Foldable (toList)+import Data.Functor.Identity (Identity(..)) import Data.Hashable (Hashable(..)) import Data.Int (Int8, Int16, Int32, Int64) import Data.Maybe (fromMaybe)-import Data.Monoid (Dual(..), First(..), Last(..), mappend)+import Data.Monoid (Dual(..), First(..), Last(..)) import Data.Ratio (Ratio, (%), numerator, denominator) import Data.Text (Text, pack, unpack) import Data.Time (UTCTime, ZonedTime(..), TimeZone(..))@@ -87,6 +89,7 @@ import qualified Data.IntSet as IntSet import qualified Data.Map as M import qualified Data.Set as Set+import qualified Data.Sequence as Seq import qualified Data.Tree as Tree import qualified Data.Text as T import qualified Data.Text.Lazy as LT@@ -97,6 +100,14 @@ import qualified Data.Vector.Unboxed as VU import qualified Data.Vector.Mutable as VM ( unsafeNew, unsafeWrite ) +instance (ToJSON a) => ToJSON (Identity a) where+ toJSON (Identity a) = toJSON a+ {-# INLINE toJSON #-}++instance (FromJSON a) => FromJSON (Identity a) where+ parseJSON a = Identity <$> parseJSON a+ {-# INLINE parseJSON #-}+ instance (ToJSON a) => ToJSON (Maybe a) where toJSON (Just a) = toJSON a toJSON Nothing = Null@@ -339,6 +350,14 @@ parseJSON = withArray "[a]" $ mapM parseJSON . V.toList {-# INLINE parseJSON #-} +instance (ToJSON a) => ToJSON (Seq.Seq a) where+ toJSON = toJSON . toList+ {-# INLINE toJSON #-}++instance (FromJSON a) => FromJSON (Seq.Seq a) where+ parseJSON = withArray "Seq a" $ traverse parseJSON . Seq.fromList . V.toList+ {-# INLINE parseJSON #-}+ instance (ToJSON a) => ToJSON (Vector a) where toJSON = Array . V.map toJSON {-# INLINE toJSON #-}@@ -482,8 +501,11 @@ | otherwise = "%z" formatMillis :: (FormatTime t) => t -> String-formatMillis t = take 3 . formatTime defaultTimeLocale "%q" $ t+formatMillis = take 3 . formatSubseconds +formatSubseconds :: (FormatTime t) => t -> String+formatSubseconds = formatTime defaultTimeLocale "%q"+ instance FromJSON ZonedTime where parseJSON (String t) = tryFormats alternateFormats@@ -495,20 +517,32 @@ Nothing -> empty tryFormats = foldr1 (<|>) . map tryFormat alternateFormats =- dateTimeFmt defaultTimeLocale :- distributeList ["%Y", "%Y-%m", "%F"]- ["T%R", "T%T", "T%T%Q", "T%T%QZ", "T%T%Q%z"]-- distributeList xs ys =- foldr (\x acc -> acc ++ distribute x ys) [] xs- distribute x = map (mappend x)+ "%FT%T%QZ" : -- (javascript new Date().toISOString())+ "%F %T%Q%z" : -- (postgres)+ "%F %T%Q %Z" : -- (time's Show format)+ "%FT%T%Q%z" :+ "%Y-%mT%T%Q" :+ "%Y-%mT%R" :+ "%Y-%mT%T" :+ "%Y-%mT%T%QZ" :+ "%Y-%mT%T%Q%z" :+ "%YT%T%Q" :+ "%YT%R" :+ "%YT%T" :+ "%YT%T%QZ" :+ "%YT%T%Q%z" :+ "%FT%T%Q" :+ "%FT%R" :+ "%FT%T" :+ dateTimeFmt defaultTimeLocale :+ [] parseJSON v = typeMismatch "ZonedTime" v instance ToJSON UTCTime where toJSON t = String $ pack $ formatTime defaultTimeLocale format t where- format = "%FT%T." ++ formatMillis t ++ "Z"+ format = "%FT%T." ++ formatSubseconds t ++ "Z" {-# INLINE toJSON #-} instance FromJSON UTCTime where
Data/Aeson/Types/Internal.hs view
@@ -180,7 +180,7 @@ | Number !Scientific | Bool !Bool | Null- deriving (Eq, Show, Typeable, Data)+ deriving (Eq, Read, Show, Typeable, Data) -- | A newtype wrapper for 'UTCTime' that uses the same non-standard -- serialization format as Microsoft .NET, whose @System.DateTime@
aeson.cabal view
@@ -1,9 +1,9 @@ name: aeson-version: 0.8.0.2+version: 0.8.1.0 license: BSD3 license-file: LICENSE category: Text, Web, JSON-copyright: (c) 2011-2014 Bryan O'Sullivan+copyright: (c) 2011-2015 Bryan O'Sullivan (c) 2011 MailRank, Inc. author: Bryan O'Sullivan <bos@serpentine.com> maintainer: Bryan O'Sullivan <bos@serpentine.com>@@ -99,7 +99,7 @@ Data.Aeson.Types.Internal build-depends:- attoparsec >= 0.11.3.4,+ attoparsec >= 0.13.0.0, base == 4.*, bytestring >= 0.10.4.0, containers,@@ -112,6 +112,7 @@ syb, template-haskell >= 2.4, text >= 1.1.1.0,+ transformers, unordered-containers >= 0.2.3.0, vector >= 0.7.1 @@ -121,7 +122,6 @@ build-depends: time >= 1.5 if flag(developer)- ghc-options: -Werror ghc-prof-options: -auto-all ghc-options: -O2 -Wall@@ -151,6 +151,7 @@ bytestring, containers, ghc-prim >= 0.2,+ old-locale, template-haskell, test-framework, test-framework-hunit,
benchmarks/aeson-benchmarks.cabal view
@@ -2,17 +2,70 @@ version: 0 build-type: Simple -cabal-version: >=1.2+cabal-version: >=1.8 +flag old-locale+ description: If false then depend on time >= 1.5.+ .+ If true then depend on time < 1.5 together with old-locale.+ default: False++library+ hs-source-dirs: ..++ exposed-modules:+ Data.Aeson+ Data.Aeson.Encode+ Data.Aeson.Generic+ Data.Aeson.Parser+ Data.Aeson.Types+ Data.Aeson.TH++ other-modules:+ Data.Aeson.Encode.ByteString+ Data.Aeson.Functions+ Data.Aeson.Parser.Internal+ Data.Aeson.Types.Class+ Data.Aeson.Types.Generic+ Data.Aeson.Types.Instances+ Data.Aeson.Types.Internal++ build-depends:+ attoparsec >= 0.13.0.0,+ base == 4.*,+ bytestring >= 0.10.4.0,+ containers,+ deepseq,+ dlist >= 0.2,+ ghc-prim >= 0.2,+ hashable >= 1.1.2.0,+ mtl,+ scientific >= 0.3.1 && < 0.4,+ syb,+ template-haskell >= 2.4,+ text >= 1.1.1.0,+ transformers,+ unordered-containers >= 0.2.3.0,+ vector >= 0.7.1++ if flag(old-locale)+ build-depends: time < 1.5, old-locale+ else+ build-depends: time >= 1.5++ ghc-options: -O2 -Wall+ cpp-options: -DGENERICS+ executable aeson-benchmark-compare-with-json main-is: CompareWithJSON.hs ghc-options: -Wall -O2 -rtsopts build-depends:- aeson,+ aeson-benchmarks, base, blaze-builder, bytestring, criterion,+ deepseq, json, text @@ -20,16 +73,22 @@ main-is: AesonEncode.hs ghc-options: -Wall -O2 -rtsopts build-depends:- aeson,- base+ aeson-benchmarks,+ attoparsec,+ base,+ bytestring,+ deepseq,+ time executable aeson-benchmark-aeson-parse main-is: AesonParse.hs ghc-options: -Wall -O2 -rtsopts build-depends:- aeson,+ aeson-benchmarks, attoparsec,- base+ base,+ bytestring,+ time executable aeson-benchmark-json-parse main-is: JsonParse.hs
+ benchmarks/json-data/sigma.json view
file too large to diff
changelog.md view
@@ -1,3 +1,29 @@+0.8.1.0++* Encoding a Scientific value with a huge exponent is now handled+ efficiently. (This would previously allocate a huge+ arbitrary-precision integer, potentially leading to a denial of+ service.)++* Handling of strings that contain backslash escape sequences is+ greatly improved. For a pathological string containing almost a+ megabyte of consecutive backslashes, the new implementation is 27x+ faster and uses 42x less memory.++* The ToJSON instance for UTCTime is rendered with higher (picosecond)+ resolution.++* The value parser now correctly handles leading whitespace.++* New instances of ToJSON and FromJSON for Data.Sequence and+ Data.Functor.Identity. The Value type now has a Read instance.++* ZonedTime parser ordering now favours the standard JSON format,+ increasing efficiency in the common case.++* Encoding to a Text.Builder now escapes '<' and '>' characters, to+ reduce XSS risk.+ 0.8.0.2 * Fix ToJSON instance for 15-tuples (see #223).
examples/TemplateHaskell.hs view
@@ -14,10 +14,6 @@ deriving (Show) -- This splice will derive instances of ToJSON and FromJSON for us.------ The use of "id" below is a placeholder function to transform the--- names of the type's fields. We don't want to transform them, so we--- use the identity function. $(deriveJSON defaultOptions ''Coord)
tests/Properties.hs view
@@ -18,8 +18,7 @@ import qualified Data.Text.Lazy.Builder as TLB import qualified Data.Text.Lazy.Encoding as TLE import qualified Data.HashMap.Strict as H-import Data.Time.Clock (UTCTime(..))-import Data.Time (ZonedTime(..))+import Data.Time import Instances () import Types import Encoders@@ -29,9 +28,14 @@ import qualified Data.Map as Map #endif +import System.IO+import System.IO.Unsafe (unsafePerformIO)+import System.Locale roundTripCamel :: String -> Assertion roundTripCamel name = assertEqual "" name (camelFrom '_' $ camelTo '_' name)++ where camelFrom c s = let (p:ps) = split c s in concat $ p : map capitalize ps@@ -67,6 +71,71 @@ Error _ -> False Success x' -> x == x' +zonedTimeToJSON :: ZonedTime -> Bool+zonedTimeToJSON t = and $+ toFromJSON' "%FT%T%QZ" t (clearTimeZone t) : -- (javascript new Date().toISOString())+ toFromJSON' "%F %T%Q%z" t t : -- (postgres)+ toFromJSON' "%F %T%Q %Z" t t : -- (time's Show format)+ toFromJSON' "%FT%T%Q%z" t t :+ toFromJSON' "%Y-%mT%T%Q" t (clearTimeZone . clearDay $ t) :+ toFromJSON' "%Y-%mT%R" t (clearTimeZone . clearDay . clearSeconds $ t) :+ toFromJSON' "%Y-%mT%T" t (clearTimeZone . clearDay $ t) :+ toFromJSON' "%Y-%mT%T%QZ" t (clearTimeZone . clearDay $ t) :+ toFromJSON' "%Y-%mT%T%Q%z" t (clearDay t) :+ toFromJSON' "%YT%T%Q" t (clearTimeZone . clearMonth . clearDay $ t) :+ toFromJSON' "%YT%R" t (clearTimeZone . clearMonth . clearDay . clearSeconds $ t) :+ toFromJSON' "%YT%T" t (clearTimeZone . clearMonth . clearDay $ t) :+ toFromJSON' "%YT%T%QZ" t (clearTimeZone . clearMonth . clearDay $ t) :+ toFromJSON' "%YT%T%Q%z" t (clearMonth . clearDay $ t) :+ toFromJSON' "%FT%T%Q" t (clearTimeZone t) :+ toFromJSON' "%FT%R" t (clearTimeZone . clearSeconds $ t) :+ toFromJSON' "%FT%T" t (clearTimeZone t) :+ toFromJSON' (dateTimeFmt defaultTimeLocale) t t :+ []+ where+ toJSON' :: String -> ZonedTime -> Value+ toJSON' format = toJSON . formatTime defaultTimeLocale format++ toFromJSON' :: String -> ZonedTime -> ZonedTime -> Bool+ toFromJSON' format t_ t_' = case fromJSON . toJSON' format $ t_ of+ Error msg -> error' msg+ Success t_'' -> if t_'' == t_' then True else error' (show t_'')+ where+ error' :: String -> Bool+ error' msg = unsafePerformIO (hPutStrLn stderr . ("zonedTimeToJSON: " ++) $ show+ (format, t_, toJSON' format t_, t_', msg))+ `seq` False++clearTimeZone :: ZonedTime -> ZonedTime+clearTimeZone t = t { zonedTimeZone = TimeZone 0 False "" }++clearMonth :: ZonedTime -> ZonedTime+clearMonth = f+ where+ f zt = zt { zonedTimeToLocalTime = g $ zonedTimeToLocalTime zt }+ g lt = lt { localDay = h $ localDay lt }++ h :: Day -> Day+ h = maybe (error "clearMonth") id . parseTime defaultTimeLocale "%Y-%m-%d" . formatTime defaultTimeLocale "%Y-01-%d"++clearDay :: ZonedTime -> ZonedTime+clearDay = f+ where+ f zt = zt { zonedTimeToLocalTime = g $ zonedTimeToLocalTime zt }+ g lt = lt { localDay = h $ localDay lt }++ h :: Day -> Day+ h = maybe (error "clearDay") id . parseTime defaultTimeLocale "%Y-%m-%d" . formatTime defaultTimeLocale "%Y-%m-01"++clearSeconds :: ZonedTime -> ZonedTime+clearSeconds = f+ where+ f zt = zt { zonedTimeToLocalTime = g $ zonedTimeToLocalTime zt }+ g lt = lt { localTimeOfDay = h $ localTimeOfDay lt }++ h :: TimeOfDay -> TimeOfDay+ h = maybe (error "clearSeconds") id . parseTime defaultTimeLocale "%H:%M:%S" . formatTime defaultTimeLocale "%H:%M:00"+ modifyFailureProp :: String -> String -> Bool modifyFailureProp orig added = result == Error (added ++ orig)@@ -126,14 +195,14 @@ testGroup "roundTrip" [ testProperty "Bool" $ roundTripEq True , testProperty "Double" $ roundTripEq (1 :: Approx Double)- , testProperty "Int" $ roundTripEq (1::Int)- , testProperty "Integer" $ roundTripEq (1::Integer)- , testProperty "String" $ roundTripEq (""::String)+ , testProperty "Int" $ roundTripEq (1 :: Int)+ , testProperty "Integer" $ roundTripEq (1 :: Integer)+ , testProperty "String" $ roundTripEq ("" :: String) , testProperty "Text" $ roundTripEq T.empty- , testProperty "Foo" $ roundTripEq (undefined::Foo)+ , testProperty "Foo" $ roundTripEq (undefined :: Foo) , testProperty "DotNetTime" $ roundTripEq (undefined :: DotNetTime) , testProperty "UTCTime" $ roundTripEq (undefined :: UTCTime)- , testProperty "ZonedTime" $ roundTripEq (undefined::ZonedTime)+ , testProperty "ZonedTime" $ roundTripEq (undefined :: ZonedTime) #ifdef GHC_GENERICS , testGroup "ghcGenerics" [ testProperty "OneConstructor" $ roundTripEq OneConstructor@@ -149,6 +218,7 @@ , testProperty "Maybe Integer" (toFromJSON :: Maybe Integer -> Bool) , testProperty "Either Integer Double" (toFromJSON :: Either Integer Double -> Bool) , testProperty "Either Integer Integer" (toFromJSON :: Either Integer Integer -> Bool)+ , testProperty "ZonedTime" $ zonedTimeToJSON ], testGroup "deprecated" deprecatedTests, testGroup "failure messages" [