on master help sign in
.gitignore+4
since splitsnapshots
unifiedsplit
@@ -5,6 +5,7 @@ dist-newstyle
5cabal.sandbox.config
5cabal.sandbox.config
6.stack-work/
6.stack-work/
7.stack-work-bench/
7.stack-work-bench/
8+*.yaml.lock
8
9
9*.o
10*.o
10*.hi
11*.hi
@@ -13,6 +14,9 @@ cabal.sandbox.config
13.\#*
14.\#*
14\#*
15\#*
15
16
17+# Emacs backup files
18+*~
19+
16benchmarks/AesonEncode
20benchmarks/AesonEncode
17
21
18tests/qc
22tests/qc
.hlint.yamlnew file+11
@@ -0,0 +1,11 @@
1+# Ignore some builtin hints
2+- ignore:
3+ name: "Reduce duplication"
4+ within:
5+ - Data.Aeson.Types.ToJSON
6+- ignore:
7+ name: "Eta reduce"
8+- ignore:
9+ name: "Use newtype instead of data"
10+ within:
11+ - Data.Aeson.Types.Internal
.travis.yml+42−21
@@ -12,38 +12,59 @@ cache:
12 - $HOME/.stack
12 - $HOME/.stack
13 - $HOME/.cabsnap
13 - $HOME/.cabsnap
14 - $HOME/.cabal/packages
14 - $HOME/.cabal/packages
15+ - $HOME/.cabal/store
15
16
16before_cache:
17before_cache:
17 - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
18 - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
18− - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.tar
19+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*
20+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json
21+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache
22+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar
23+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx
19
24
20matrix:
25matrix:
21 include:
26 include:
27+
22− - env: BUILD=hlint CABALVER=1.24 GHCVER=8.0.2
28+ - env: BUILD=hlint CABALVER=2.2 GHCVER=8.4.4
23− compiler: ": #GHC 8.0.2"
29+ compiler: ": #GHC 8.4.4"
24− addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.2], sources: [hvr-ghc]}}
30+ addons: {apt: {packages: [cabal-install-2.2,ghc-8.4.4], sources: [hvr-ghc]}}
31+
25− - env: BUILD=stack CABALVER=1.24 GHCVER=8.0.2 STACK_YAML=stack-bench.yaml
32+ - env: BUILD=stack CABALVER=2.4 GHCVER=8.6.5 STACK_YAML=stack-bench.yaml
26− compiler: ": #GHC 8.0.2"
33+ compiler: ": #GHC 8.6.5"
27− addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.2], sources: [hvr-ghc]}}
34+ addons: {apt: {packages: [cabal-install-2.4,ghc-8.6.5], sources: [hvr-ghc]}}
35+
28− - env: BUILD=stack CABALVER=1.24 GHCVER=8.0.2 STACK_YAML=stack-pure-unescape.yaml
36+ - env: BUILD=stack CABALVER=2.2 GHCVER=8.6.5 STACK_YAML=stack-ffi-unescape.yaml
29− compiler: ": #GHC 8.0.2"
37+ compiler: ": #GHC 8.6.5"
38+ addons: {apt: {packages: [cabal-install-2.2,ghc-8.6.5], sources: [hvr-ghc]}}
39+
40+ - env: BUILD=cabal2 CABALVER=3.0 GHCVER=8.8.1
41+ compiler: ": #GHC 8.8.1"
30− addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.2], sources: [hvr-ghc]}}
42+ addons: {apt: {packages: [cabal-install-3.0,ghc-8.8.1], sources: [hvr-ghc]}}
43+
44+ - env: BUILD=cabal CABALVER=2.4 GHCVER=8.6.5
45+ compiler: ": #GHC 8.6.5"
46+ addons: {apt: {packages: [cabal-install-2.4,ghc-8.6.5], sources: [hvr-ghc]}}
47+
48+ - env: BUILD=cabal CABALVER=2.2 GHCVER=8.4.4
49+ compiler: ": #GHC 8.4.4"
50+ addons: {apt: {packages: [cabal-install-2.2,ghc-8.4.4], sources: [hvr-ghc]}}
51+
52+ - env: BUILD=cabal CABALVER=2.0 GHCVER=8.2.2
53+ compiler: ": #GHC 8.2.2"
54+ addons: {apt: {packages: [cabal-install-2.0,ghc-8.2.2], sources: [hvr-ghc]}}
55+
31− - env: BUILD=cabal CABALVER=1.24 GHCVER=8.0.2
56+ - env: BUILD=cabal CABALVER=2.0 GHCVER=8.0.2
32 compiler: ": #GHC 8.0.2"
57 compiler: ": #GHC 8.0.2"
33− addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.2], sources: [hvr-ghc]}}
58+ addons: {apt: {packages: [cabal-install-2.0,ghc-8.0.2], sources: [hvr-ghc]}}
34− - env: BUILD=cabal CABALVER=1.18 GHCVER=7.4.2
59+
35− compiler: ": #GHC 7.4.2"
36− addons: {apt: {packages: [cabal-install-1.18,ghc-7.4.2], sources: [hvr-ghc]}}
37− - env: BUILD=cabal CABALVER=1.18 GHCVER=7.6.3
38− compiler: ": #GHC 7.6.3"
39− addons: {apt: {packages: [cabal-install-1.18,ghc-7.6.3], sources: [hvr-ghc]}}
40− - env: BUILD=cabal CABALVER=1.18 GHCVER=7.8.4
41− compiler: ": #GHC 7.8.4"
42− addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}}
43 - env: BUILD=cabal CABALVER=1.22 GHCVER=7.10.3
60 - env: BUILD=cabal CABALVER=1.22 GHCVER=7.10.3
44 compiler: ": #GHC 7.10.3"
61 compiler: ": #GHC 7.10.3"
45 addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.3], sources: [hvr-ghc]}}
62 addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.3], sources: [hvr-ghc]}}
46
63
64+ - env: BUILD=cabal CABALVER=1.18 GHCVER=7.8.4
65+ compiler: ": #GHC 7.8.4"
66+ addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}}
67+
47before_install:
68before_install:
48 - unset CC
69 - unset CC
49 - export PATH=$HOME/.local/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
70 - export PATH=$HOME/.local/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
CONTRIBUTING.md+38
@@ -58,3 +58,41 @@ import Foo as F
58import Qux (Q(..), q)
58import Qux (Q(..), q)
59import qualified A
59import qualified A
60```
60```
61+
62+## Development Workflow (If you plan on using Stack)
63+
64+Here we outline a general workflow new contributors could adopt for a nice incremental development experience.
65+
66+The root folder consists of two stack-*.yaml config files.
67+
68+1. stack-nightly.yaml - Config file defines all the settings to build and test any changes.
69+2. stack-bench.yaml - Used to run benchmark tests.
70+3. stack-ffi-unescape.yaml - This is for replacing certain parts with a C-implementation.
71+
72+Therefore, a ghci development experience would be:
73+
74+1. `stack ghci --stack-yaml stack-nightly.yaml --test`
75+2. :r to recompile
76+3. `main` to run all tests, or `:main --pattern Foo` to run specific tests matched by pattern.
77+
78+Some discussion on this topic can also be found here [here](https://github.com/bos/aeson/issues/733)
79+
80+Of course before submitting a PR, the following steps are recommended:
81+
82+1. 'stack test --stack-yaml stack-nightly.yaml` - Run the entire suite of tests
83+2. `make hlint` - Run hlint on the source folders.
84+3. 'stack test --stack-yaml stack-bench.yaml` - Run the benchmark tests if you believe your changes could affect the benchmarks.
85+
86+## Development Workflow (If you plan on using Cabal)
87+
88+A ghci development experience would be:
89+
90+1. `cabal new-repl test:tests`
91+2. `:r` to recompile
92+3. `Main.main` to run all tests, or `:m Main; :main --pattern Foo` to run specific tests matched by pattern.
93+
94+Of course before submitting a PR, the following steps are recommended:
95+
96+1. `cabal new-test` - Run the entire suite of tests
97+2. `make hlint` - Run hlint on the source folders.
98+3. Uncomment the `benchmarks` line in `cabal.project` - Run the benchmark tests if you believe your changes could affect the benchmarks.
Data/Aeson.hs+62−5
@@ -1,3 +1,4 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1-- |
2-- |
2-- Module: Data.Aeson
3-- Module: Data.Aeson
3-- Copyright: (c) 2011-2016 Bryan O'Sullivan
4-- Copyright: (c) 2011-2016 Bryan O'Sullivan
@@ -38,11 +39,16 @@ module Data.Aeson
38 , eitherDecode
39 , eitherDecode
39 , eitherDecode'
40 , eitherDecode'
40 , encode
41 , encode
42+ , encodeFile
41 -- ** Variants for strict bytestrings
43 -- ** Variants for strict bytestrings
42 , decodeStrict
44 , decodeStrict
45+ , decodeFileStrict
43 , decodeStrict'
46 , decodeStrict'
47+ , decodeFileStrict'
44 , eitherDecodeStrict
48 , eitherDecodeStrict
49+ , eitherDecodeFileStrict
45 , eitherDecodeStrict'
50 , eitherDecodeStrict'
51+ , eitherDecodeFileStrict'
46 -- * Core JSON types
52 -- * Core JSON types
47 , Value(..)
53 , Value(..)
48 , Encoding
54 , Encoding
@@ -57,11 +63,18 @@ module Data.Aeson
57 , fromJSON
63 , fromJSON
58 , ToJSON(..)
64 , ToJSON(..)
59 , KeyValue(..)
65 , KeyValue(..)
66+ , (<?>)
67+ , JSONPath
60 -- ** Keys for maps
68 -- ** Keys for maps
61 , ToJSONKey(..)
69 , ToJSONKey(..)
62 , ToJSONKeyFunction(..)
70 , ToJSONKeyFunction(..)
63 , FromJSONKey(..)
71 , FromJSONKey(..)
64 , FromJSONKeyFunction(..)
72 , FromJSONKeyFunction(..)
73+ -- *** Generic keys
74+ , GToJSONKey()
75+ , genericToJSONKey
76+ , GFromJSONKey()
77+ , genericFromJSONKey
65 -- ** Liftings to unary and binary type constructors
78 -- ** Liftings to unary and binary type constructors
66 , FromJSON1(..)
79 , FromJSON1(..)
67 , parseJSON1
80 , parseJSON1
@@ -103,14 +116,18 @@ module Data.Aeson
103 , SumEncoding(..)
116 , SumEncoding(..)
104 , camelTo2
117 , camelTo2
105 , defaultTaggedObject
118 , defaultTaggedObject
119+ -- ** Options for object keys
120+ , JSONKeyOptions
121+ , keyModifier
122+ , defaultJSONKeyOptions
106
123
107 -- * Inspecting @'Value's@
124 -- * Inspecting @'Value's@
108 , withObject
125 , withObject
109 , withText
126 , withText
110 , withArray
127 , withArray
111− , withNumber
112 , withScientific
128 , withScientific
113 , withBool
129 , withBool
130+ , withEmbeddedJSON
114 -- * Constructors and accessors
131 -- * Constructors and accessors
115 , Series
132 , Series
116 , pairs
133 , pairs
@@ -123,16 +140,16 @@ module Data.Aeson
123 -- * Parsing
140 -- * Parsing
124 , json
141 , json
125 , json'
142 , json'
143+ , parseIndexedJSON
126 ) where
144 ) where
127
145
128−import Prelude ()
129import Prelude.Compat
146import Prelude.Compat
130
147
131−import Data.Aeson.Types.FromJSON (ifromJSON)
148+import Data.Aeson.Types.FromJSON (ifromJSON, parseIndexedJSON)
132import Data.Aeson.Encoding (encodingToLazyByteString)
149import Data.Aeson.Encoding (encodingToLazyByteString)
133import Data.Aeson.Parser.Internal (decodeWith, decodeStrictWith, eitherDecodeWith, eitherDecodeStrictWith, jsonEOF, json, jsonEOF', json')
150import Data.Aeson.Parser.Internal (decodeWith, decodeStrictWith, eitherDecodeWith, eitherDecodeStrictWith, jsonEOF, json, jsonEOF', json')
134import Data.Aeson.Types
151import Data.Aeson.Types
135−import Data.Aeson.Types.Internal (JSONPath, formatError)
152+import Data.Aeson.Types.Internal (JSONPath, formatError, (<?>))
136import qualified Data.ByteString as B
153import qualified Data.ByteString as B
137import qualified Data.ByteString.Lazy as L
154import qualified Data.ByteString.Lazy as L
138
155
@@ -142,6 +159,10 @@ import qualified Data.ByteString.Lazy as L
142encode :: (ToJSON a) => a -> L.ByteString
159encode :: (ToJSON a) => a -> L.ByteString
143encode = encodingToLazyByteString . toEncoding
160encode = encodingToLazyByteString . toEncoding
144
161
162+-- | Efficiently serialize a JSON value as a lazy 'L.ByteString' and write it to a file.
163+encodeFile :: (ToJSON a) => FilePath -> a -> IO ()
164+encodeFile fp = L.writeFile fp . encode
165+
145-- | Efficiently deserialize a JSON value from a lazy 'L.ByteString'.
166-- | Efficiently deserialize a JSON value from a lazy 'L.ByteString'.
146-- If this fails due to incomplete or invalid input, 'Nothing' is
167-- If this fails due to incomplete or invalid input, 'Nothing' is
147-- returned.
168-- returned.
@@ -168,6 +189,18 @@ decodeStrict :: (FromJSON a) => B.ByteString -> Maybe a
168decodeStrict = decodeStrictWith jsonEOF fromJSON
189decodeStrict = decodeStrictWith jsonEOF fromJSON
169{-# INLINE decodeStrict #-}
190{-# INLINE decodeStrict #-}
170
191
192+-- | Efficiently deserialize a JSON value from a file.
193+-- If this fails due to incomplete or invalid input, 'Nothing' is
194+-- returned.
195+--
196+-- The input file's content must consist solely of a JSON document,
197+-- with no trailing data except for whitespace.
198+--
199+-- This function parses immediately, but defers conversion. See
200+-- 'json' for details.
201+decodeFileStrict :: (FromJSON a) => FilePath -> IO (Maybe a)
202+decodeFileStrict = fmap decodeStrict . B.readFile
203+
171-- | Efficiently deserialize a JSON value from a lazy 'L.ByteString'.
204-- | Efficiently deserialize a JSON value from a lazy 'L.ByteString'.
172-- If this fails due to incomplete or invalid input, 'Nothing' is
205-- If this fails due to incomplete or invalid input, 'Nothing' is
173-- returned.
206-- returned.
@@ -194,6 +227,18 @@ decodeStrict' :: (FromJSON a) => B.ByteString -> Maybe a
194decodeStrict' = decodeStrictWith jsonEOF' fromJSON
227decodeStrict' = decodeStrictWith jsonEOF' fromJSON
195{-# INLINE decodeStrict' #-}
228{-# INLINE decodeStrict' #-}
196
229
230+-- | Efficiently deserialize a JSON value from a file.
231+-- If this fails due to incomplete or invalid input, 'Nothing' is
232+-- returned.
233+--
234+-- The input file's content must consist solely of a JSON document,
235+-- with no trailing data except for whitespace.
236+--
237+-- This function parses and performs conversion immediately. See
238+-- 'json'' for details.
239+decodeFileStrict' :: (FromJSON a) => FilePath -> IO (Maybe a)
240+decodeFileStrict' = fmap decodeStrict' . B.readFile
241+
197eitherFormatError :: Either (JSONPath, String) a -> Either String a
242eitherFormatError :: Either (JSONPath, String) a -> Either String a
198eitherFormatError = either (Left . uncurry formatError) Right
243eitherFormatError = either (Left . uncurry formatError) Right
199{-# INLINE eitherFormatError #-}
244{-# INLINE eitherFormatError #-}
@@ -209,6 +254,12 @@ eitherDecodeStrict =
209 eitherFormatError . eitherDecodeStrictWith jsonEOF ifromJSON
254 eitherFormatError . eitherDecodeStrictWith jsonEOF ifromJSON
210{-# INLINE eitherDecodeStrict #-}
255{-# INLINE eitherDecodeStrict #-}
211
256
257+-- | Like 'decodeFileStrict' but returns an error message when decoding fails.
258+eitherDecodeFileStrict :: (FromJSON a) => FilePath -> IO (Either String a)
259+eitherDecodeFileStrict =
260+ fmap (eitherFormatError . eitherDecodeStrictWith jsonEOF ifromJSON) . B.readFile
261+{-# INLINE eitherDecodeFileStrict #-}
262+
212-- | Like 'decode'' but returns an error message when decoding fails.
263-- | Like 'decode'' but returns an error message when decoding fails.
213eitherDecode' :: (FromJSON a) => L.ByteString -> Either String a
264eitherDecode' :: (FromJSON a) => L.ByteString -> Either String a
214eitherDecode' = eitherFormatError . eitherDecodeWith jsonEOF' ifromJSON
265eitherDecode' = eitherFormatError . eitherDecodeWith jsonEOF' ifromJSON
@@ -220,6 +271,12 @@ eitherDecodeStrict' =
220 eitherFormatError . eitherDecodeStrictWith jsonEOF' ifromJSON
271 eitherFormatError . eitherDecodeStrictWith jsonEOF' ifromJSON
221{-# INLINE eitherDecodeStrict' #-}
272{-# INLINE eitherDecodeStrict' #-}
222
273
274+-- | Like 'decodeFileStrict'' but returns an error message when decoding fails.
275+eitherDecodeFileStrict' :: (FromJSON a) => FilePath -> IO (Either String a)
276+eitherDecodeFileStrict' =
277+ fmap (eitherFormatError . eitherDecodeStrictWith jsonEOF' ifromJSON) . B.readFile
278+{-# INLINE eitherDecodeFileStrict' #-}
279+
223-- $use
280-- $use
224--
281--
225-- This section contains basic information on the different ways to
282-- This section contains basic information on the different ways to
@@ -381,7 +438,7 @@ eitherDecodeStrict' =
381-- The 'Object' type contains JSON objects:
438-- The 'Object' type contains JSON objects:
382--
439--
383-- > λ> decode "{\"name\":\"Dave\",\"age\":2}" :: Maybe Object
440-- > λ> decode "{\"name\":\"Dave\",\"age\":2}" :: Maybe Object
384−-- > Just (fromList) [("name",String "Dave"),("age",Number 2)]
441+-- > Just (fromList [("name",String "Dave"),("age",Number 2)])
385--
442--
386-- You can extract values from it with a parser using 'parse',
443-- You can extract values from it with a parser using 'parse',
387-- 'parseEither' or, in this example, 'parseMaybe':
444-- 'parseEither' or, in this example, 'parseMaybe':
Data/Aeson/Encode.hs+2−1
@@ -1,3 +1,4 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1-- |
2-- |
2-- Module: Data.Aeson.Encode
3-- Module: Data.Aeson.Encode
3-- Copyright: (c) 2012-2016 Bryan O'Sullivan
4-- Copyright: (c) 2012-2016 Bryan O'Sullivan
@@ -8,13 +9,13 @@
8-- Portability: portable
9-- Portability: portable
9--
10--
10-- This module is left to supply limited backwards-compatibility.
11-- This module is left to supply limited backwards-compatibility.
12+
11module Data.Aeson.Encode {-# DEPRECATED "Use Data.Aeson or Data.Aeson.Text instead" #-}
13module Data.Aeson.Encode {-# DEPRECATED "Use Data.Aeson or Data.Aeson.Text instead" #-}
12 (
14 (
13 encode
15 encode
14 , encodeToTextBuilder
16 , encodeToTextBuilder
15 ) where
17 ) where
16
18
17−import Prelude ()
18
19
19import Data.ByteString.Lazy (ByteString)
20import Data.ByteString.Lazy (ByteString)
20import Data.Text.Lazy.Builder (Builder)
21import Data.Text.Lazy.Builder (Builder)
Data/Aeson/Encoding.hs+2−1
@@ -1,8 +1,10 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1-- |
2-- |
2--
3--
3-- Functions in this module return well-formed 'Encoding''.
4-- Functions in this module return well-formed 'Encoding''.
4-- Polymorphic variants, which return @'Encoding' a@, return a textual JSON
5-- Polymorphic variants, which return @'Encoding' a@, return a textual JSON
5-- value, so it can be used as both @'Encoding'' 'Text'@ and @'Encoding' = 'Encoding'' 'Value'@.
6-- value, so it can be used as both @'Encoding'' 'Text'@ and @'Encoding' = 'Encoding'' 'Value'@.
7+
6module Data.Aeson.Encoding
8module Data.Aeson.Encoding
7 (
9 (
8 -- * Encoding
10 -- * Encoding
@@ -49,6 +51,5 @@ module Data.Aeson.Encoding
49 , value
51 , value
50 ) where
52 ) where
51
53
52−import Prelude ()
53
54
54import Data.Aeson.Encoding.Internal
55import Data.Aeson.Encoding.Internal
Data/Aeson/Encoding/Builder.hs+6−79
@@ -1,6 +1,6 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2−{-# LANGUAGE CPP #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
3−
3+{-# LANGUAGE TupleSections #-}
4-- |
4-- |
5-- Module: Data.Aeson.Encoding.Builder
5-- Module: Data.Aeson.Encoding.Builder
6-- Copyright: (c) 2011 MailRank, Inc.
6-- Copyright: (c) 2011 MailRank, Inc.
@@ -36,7 +36,6 @@ module Data.Aeson.Encoding.Builder
36 , ascii5
36 , ascii5
37 ) where
37 ) where
38
38
39−import Prelude ()
40import Prelude.Compat
39import Prelude.Compat
41
40
42import Data.Aeson.Internal.Time
41import Data.Aeson.Internal.Time
@@ -45,8 +44,9 @@ import Data.ByteString.Builder as B
45import Data.ByteString.Builder.Prim as BP
44import Data.ByteString.Builder.Prim as BP
46import Data.ByteString.Builder.Scientific (scientificBuilder)
45import Data.ByteString.Builder.Scientific (scientificBuilder)
47import Data.Char (chr, ord)
46import Data.Char (chr, ord)
48−import Data.Monoid ((<>))
47+import Data.Semigroup ((<>))
49import Data.Scientific (Scientific, base10Exponent, coefficient)
48import Data.Scientific (Scientific, base10Exponent, coefficient)
49+import Data.Text.Encoding (encodeUtf8BuilderEscaped)
50import Data.Time (UTCTime(..))
50import Data.Time (UTCTime(..))
51import Data.Time.Calendar (Day(..), toGregorian)
51import Data.Time.Calendar (Day(..), toGregorian)
52import Data.Time.LocalTime
52import Data.Time.LocalTime
@@ -55,20 +55,6 @@ import qualified Data.HashMap.Strict as HMS
55import qualified Data.Text as T
55import qualified Data.Text as T
56import qualified Data.Vector as V
56import qualified Data.Vector as V
57
57
58−#if MIN_VERSION_bytestring(0,10,4)
59−import Data.Text.Encoding (encodeUtf8BuilderEscaped)
60−#else
61−import Data.Bits ((.&.))
62−import Data.Text.Internal (Text(..))
63−import Data.Text.Internal.Unsafe.Shift (shiftR)
64−import Foreign.Ptr (minusPtr, plusPtr)
65−import Foreign.Storable (poke)
66−import qualified Data.ByteString.Builder.Internal as B
67−import qualified Data.ByteString.Builder.Prim.Internal as BP
68−import qualified Data.Text.Array as A
69−import qualified Data.Text.Internal.Encoding.Utf16 as U16
70−#endif
71−
72-- | Encode a JSON value to a "Data.ByteString" 'B.Builder'.
58-- | Encode a JSON value to a "Data.ByteString" 'B.Builder'.
73--
59--
74-- Use this function if you are encoding over the wire, or need to
60-- Use this function if you are encoding over the wire, or need to
@@ -147,7 +133,7 @@ c2w c = fromIntegral (ord c)
147-- | Encode a JSON number.
133-- | Encode a JSON number.
148scientific :: Scientific -> Builder
134scientific :: Scientific -> Builder
149scientific s
135scientific s
150− | e < 0 = scientificBuilder s
136+ | e < 0 || e > 1024 = scientificBuilder s
151 | otherwise = B.integerDec (coefficient s * 10 ^ e)
137 | otherwise = B.integerDec (coefficient s * 10 ^ e)
152 where
138 where
153 e = base10Exponent s
139 e = base10Exponent s
@@ -215,7 +201,7 @@ timeOfDay64 (TOD h m s)
215 !(T mh ml) = twoDigits m
201 !(T mh ml) = twoDigits m
216 !(T sh sl) = twoDigits (fromIntegral real)
202 !(T sh sl) = twoDigits (fromIntegral real)
217 (real,frac) = s `quotRem` pico
203 (real,frac) = s `quotRem` pico
218− showFrac = (\x -> ('.', x)) >$< (BP.liftFixedToBounded BP.char7 >*< trunc12)
204+ showFrac = ('.',) >$< (BP.liftFixedToBounded BP.char7 >*< trunc12)
219 trunc12 = (`quotRem` micro) >$<
205 trunc12 = (`quotRem` micro) >$<
220 BP.condB (\(_,y) -> y == 0) (fst >$< trunc6) (digits6 >*< trunc6)
206 BP.condB (\(_,y) -> y == 0) (fst >$< trunc6) (digits6 >*< trunc6)
221 digits6 = ((`quotRem` milli) . fromIntegral) >$< (digits3 >*< digits3)
207 digits6 = ((`quotRem` milli) . fromIntegral) >$< (digits3 >*< digits3)
@@ -268,62 +254,3 @@ twoDigits a = T (digit hi) (digit lo)
268
254
269digit :: Int -> Char
255digit :: Int -> Char
270digit x = chr (x + 48)
256digit x = chr (x + 48)
271−
272−#if !(MIN_VERSION_bytestring(0,10,4))
273−-- | Encode text using UTF-8 encoding and escape the ASCII characters using
274−-- a 'BP.BoundedPrim'.
275−--
276−-- Use this function is to implement efficient encoders for text-based formats
277−-- like JSON or HTML.
278−{-# INLINE encodeUtf8BuilderEscaped #-}
279−-- TODO: Extend documentation with references to source code in @blaze-html@
280−-- or @aeson@ that uses this function.
281−encodeUtf8BuilderEscaped :: BP.BoundedPrim Word8 -> Text -> B.Builder
282−encodeUtf8BuilderEscaped be =
283− -- manual eta-expansion to ensure inlining works as expected
284− \txt -> B.builder (mkBuildstep txt)
285− where
286− bound = max 4 $ BP.sizeBound be
287−
288− mkBuildstep (Text arr off len) !k =
289− outerLoop off
290− where
291− iend = off + len
292−
293− outerLoop !i0 br@(B.BufferRange op0 ope)
294− | i0 >= iend = k br
295− | outRemaining > 0 = goPartial (i0 + min outRemaining inpRemaining)
296− -- TODO: Use a loop with an integrated bound's check if outRemaining
297− -- is smaller than 8, as this will save on divisions.
298− | otherwise = return $ B.bufferFull bound op0 (outerLoop i0)
299− where
300− outRemaining = (ope `minusPtr` op0) `div` bound
301− inpRemaining = iend - i0
302−
303− goPartial !iendTmp = go i0 op0
304− where
305− go !i !op
306− | i < iendTmp = case A.unsafeIndex arr i of
307− w | w <= 0x7F ->
308− BP.runB be (fromIntegral w) op >>= go (i + 1)
309− | w <= 0x7FF -> do
310− poke8 0 $ (w `shiftR` 6) + 0xC0
311− poke8 1 $ (w .&. 0x3f) + 0x80
312− go (i + 1) (op `plusPtr` 2)
313− | 0xD800 <= w && w <= 0xDBFF -> do
314− let c = ord $ U16.chr2 w (A.unsafeIndex arr (i+1))
315− poke8 0 $ (c `shiftR` 18) + 0xF0
316− poke8 1 $ ((c `shiftR` 12) .&. 0x3F) + 0x80
317− poke8 2 $ ((c `shiftR` 6) .&. 0x3F) + 0x80
318− poke8 3 $ (c .&. 0x3F) + 0x80
319− go (i + 2) (op `plusPtr` 4)
320− | otherwise -> do
321− poke8 0 $ (w `shiftR` 12) + 0xE0
322− poke8 1 $ ((w `shiftR` 6) .&. 0x3F) + 0x80
323− poke8 2 $ (w .&. 0x3F) + 0x80
324− go (i + 1) (op `plusPtr` 3)
325− | otherwise =
326− outerLoop i (B.BufferRange op ope)
327− where
328− poke8 j v = poke (op `plusPtr` j) (fromIntegral v :: Word8)
329−#endif
Data/Aeson/Encoding/Internal.hs+2−1
@@ -1,7 +1,9 @@
1{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE EmptyDataDecls #-}
2{-# LANGUAGE EmptyDataDecls #-}
3+{-# LANGUAGE NoImplicitPrelude #-}
3{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE RankNTypes #-}
5{-# LANGUAGE RankNTypes #-}
6+
5module Data.Aeson.Encoding.Internal
7module Data.Aeson.Encoding.Internal
6 (
8 (
7 -- * Encoding
9 -- * Encoding
@@ -55,7 +57,6 @@ module Data.Aeson.Encoding.Internal
55 , comma, colon, openBracket, closeBracket, openCurly, closeCurly
57 , comma, colon, openBracket, closeBracket, openCurly, closeCurly
56 ) where
58 ) where
57
59
58−import Prelude ()
59import Prelude.Compat
60import Prelude.Compat
60
61
61import Data.Aeson.Types.Internal (Value)
62import Data.Aeson.Types.Internal (Value)
Data/Aeson/Internal.hs+1−1
@@ -1,3 +1,4 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1-- |
2-- |
2-- Module: Data.Aeson.Internal
3-- Module: Data.Aeson.Internal
3-- Copyright: (c) 2015-2016 Bryan O'Sullivan
4-- Copyright: (c) 2015-2016 Bryan O'Sullivan
@@ -22,7 +23,6 @@ module Data.Aeson.Internal
22 , iparse
23 , iparse
23 ) where
24 ) where
24
25
25−import Prelude ()
26
26
27import Data.Aeson.Types.Internal
27import Data.Aeson.Types.Internal
28import Data.Aeson.Types.FromJSON (ifromJSON)
28import Data.Aeson.Types.FromJSON (ifromJSON)
Data/Aeson/Internal/Functions.hs+1−1
@@ -1,3 +1,4 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1-- |
2-- |
2-- Module: Data.Aeson.Functions
3-- Module: Data.Aeson.Functions
3-- Copyright: (c) 2011-2016 Bryan O'Sullivan
4-- Copyright: (c) 2011-2016 Bryan O'Sullivan
@@ -14,7 +15,6 @@ module Data.Aeson.Internal.Functions
14 , mapKey
15 , mapKey
15 ) where
16 ) where
16
17
17−import Prelude ()
18import Prelude.Compat
18import Prelude.Compat
19
19
20import Data.Hashable (Hashable)
20import Data.Hashable (Hashable)
Data/Aeson/Parser.hs+12−2
@@ -1,3 +1,4 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1-- |
2-- |
2-- Module: Data.Aeson.Parser
3-- Module: Data.Aeson.Parser
3-- Copyright: (c) 2012-2016 Bryan O'Sullivan
4-- Copyright: (c) 2012-2016 Bryan O'Sullivan
@@ -34,10 +35,20 @@ module Data.Aeson.Parser
34 , value
35 , value
35 , jstring
36 , jstring
36 , scientific
37 , scientific
38+ -- ** Handling objects with duplicate keys
39+ , jsonWith
40+ , jsonLast
41+ , jsonAccum
42+ , jsonNoDup
37 -- * Strict parsers
43 -- * Strict parsers
38 -- $strict
44 -- $strict
39 , json'
45 , json'
40 , value'
46 , value'
47+ -- ** Handling objects with duplicate keys
48+ , jsonWith'
49+ , jsonLast'
50+ , jsonAccum'
51+ , jsonNoDup'
41 -- * Decoding without FromJSON instances
52 -- * Decoding without FromJSON instances
42 , decodeWith
53 , decodeWith
43 , decodeStrictWith
54 , decodeStrictWith
@@ -45,9 +56,8 @@ module Data.Aeson.Parser
45 , eitherDecodeStrictWith
56 , eitherDecodeStrictWith
46 ) where
57 ) where
47
58
48−import Prelude ()
49
59
50−import Data.Aeson.Parser.Internal (decodeStrictWith, decodeWith, eitherDecodeStrictWith, eitherDecodeWith, json, json', jstring, scientific, value, value')
60+import Data.Aeson.Parser.Internal
51
61
52-- $lazy
62-- $lazy
53--
63--
Data/Aeson/Parser/Internal.hs+219−51
@@ -1,10 +1,14 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE CPP #-}
2{-# LANGUAGE CPP #-}
3+{-# LANGUAGE NoImplicitPrelude #-}
3{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE OverloadedStrings #-}
4#if MIN_VERSION_ghc_prim(0,3,1)
5#if MIN_VERSION_ghc_prim(0,3,1)
5{-# LANGUAGE MagicHash #-}
6{-# LANGUAGE MagicHash #-}
6#endif
7#endif
7−
8+#if __GLASGOW_HASKELL__ <= 710 && __GLASGOW_HASKELL__ >= 706
9+-- Work around a compiler bug
10+{-# OPTIONS_GHC -fsimpl-tick-factor=200 #-}
11+#endif
8-- |
12-- |
9-- Module: Data.Aeson.Parser.Internal
13-- Module: Data.Aeson.Parser.Internal
10-- Copyright: (c) 2011-2016 Bryan O'Sullivan
14-- Copyright: (c) 2011-2016 Bryan O'Sullivan
@@ -21,30 +25,43 @@ module Data.Aeson.Parser.Internal
21 (
25 (
22 -- * Lazy parsers
26 -- * Lazy parsers
23 json, jsonEOF
27 json, jsonEOF
28+ , jsonWith
29+ , jsonLast
30+ , jsonAccum
31+ , jsonNoDup
24 , value
32 , value
25 , jstring
33 , jstring
26 , jstring_
34 , jstring_
27 , scientific
35 , scientific
28 -- * Strict parsers
36 -- * Strict parsers
29 , json', jsonEOF'
37 , json', jsonEOF'
38+ , jsonWith'
39+ , jsonLast'
40+ , jsonAccum'
41+ , jsonNoDup'
30 , value'
42 , value'
31 -- * Helpers
43 -- * Helpers
32 , decodeWith
44 , decodeWith
33 , decodeStrictWith
45 , decodeStrictWith
34 , eitherDecodeWith
46 , eitherDecodeWith
35 , eitherDecodeStrictWith
47 , eitherDecodeStrictWith
48+ -- ** Handling objects with duplicate keys
49+ , fromListAccum
50+ , parseListNoDup
36 ) where
51 ) where
37
52
38−import Prelude ()
39import Prelude.Compat
53import Prelude.Compat
40
54
41import Control.Applicative ((<|>))
55import Control.Applicative ((<|>))
42import Control.Monad (void, when)
56import Control.Monad (void, when)
43−import Data.Aeson.Types.Internal (IResult(..), JSONPath, Result(..), Value(..))
57+import Data.Aeson.Types.Internal (IResult(..), JSONPath, Object, Result(..), Value(..))
44import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)
58import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)
59+import Data.Function (fix)
60+import Data.Functor.Compat (($>))
45import Data.Scientific (Scientific)
61import Data.Scientific (Scientific)
46import Data.Text (Text)
62import Data.Text (Text)
63+import Data.Vector (Vector)
47−import Data.Vector as Vector (Vector, empty, fromListN, reverse)
64+import qualified Data.Vector as Vector (empty, fromList, fromListN, reverse)
48import qualified Data.Attoparsec.ByteString as A
65import qualified Data.Attoparsec.ByteString as A
49import qualified Data.Attoparsec.Lazy as L
66import qualified Data.Attoparsec.Lazy as L
50import qualified Data.ByteString as B
67import qualified Data.ByteString as B
@@ -76,7 +93,7 @@ import qualified Data.Text.Encoding as TE
76#define C_n 110
93#define C_n 110
77#define C_t 116
94#define C_t 116
78
95
79−-- | Parse a top-level JSON value.
96+-- | Parse any JSON value.
80--
97--
81-- The conversion of a parsed value to a Haskell value is deferred
98-- The conversion of a parsed value to a Haskell value is deferred
82-- until the Haskell value is needed. This may improve performance if
99-- until the Haskell value is needed. This may improve performance if
@@ -86,10 +103,15 @@ import qualified Data.Text.Encoding as TE
86-- This function is an alias for 'value'. In aeson 0.8 and earlier, it
103-- This function is an alias for 'value'. In aeson 0.8 and earlier, it
87-- parsed only object or array types, in conformance with the
104-- parsed only object or array types, in conformance with the
88-- now-obsolete RFC 4627.
105-- now-obsolete RFC 4627.
106+--
107+-- ==== Warning
108+--
109+-- If an object contains duplicate keys, only the first one will be kept.
110+-- For a more flexible alternative, see 'jsonWith'.
89json :: Parser Value
111json :: Parser Value
90json = value
112json = value
91
113
92−-- | Parse a top-level JSON value.
114+-- | Parse any JSON value.
93--
115--
94-- This is a strict version of 'json' which avoids building up thunks
116-- This is a strict version of 'json' which avoids building up thunks
95-- during parsing; it performs all conversions immediately. Prefer
117-- during parsing; it performs all conversions immediately. Prefer
@@ -98,23 +120,35 @@ json = value
98-- This function is an alias for 'value''. In aeson 0.8 and earlier, it
120-- This function is an alias for 'value''. In aeson 0.8 and earlier, it
99-- parsed only object or array types, in conformance with the
121-- parsed only object or array types, in conformance with the
100-- now-obsolete RFC 4627.
122-- now-obsolete RFC 4627.
123+--
124+-- ==== Warning
125+--
126+-- If an object contains duplicate keys, only the first one will be kept.
127+-- For a more flexible alternative, see 'jsonWith''.
101json' :: Parser Value
128json' :: Parser Value
102json' = value'
129json' = value'
103
130
131+-- Open recursion: object_, object_', array_, array_' are parameterized by the
132+-- toplevel Value parser to be called recursively, to keep the parameter
133+-- mkObject outside of the recursive loop for proper inlining.
134+
104−object_ :: Parser Value
135+object_ :: ([(Text, Value)] -> Either String Object) -> Parser Value -> Parser Value
105−object_ = {-# SCC "object_" #-} Object <$> objectValues jstring value
136+object_ mkObject val = {-# SCC "object_" #-} Object <$> objectValues mkObject jstring val
137+{-# INLINE object_ #-}
106
138
107−object_' :: Parser Value
139+object_' :: ([(Text, Value)] -> Either String Object) -> Parser Value -> Parser Value
108−object_' = {-# SCC "object_'" #-} do
140+object_' mkObject val' = {-# SCC "object_'" #-} do
109− !vals <- objectValues jstring' value'
141+ !vals <- objectValues mkObject jstring' val'
110 return (Object vals)
142 return (Object vals)
111 where
143 where
112 jstring' = do
144 jstring' = do
113 !s <- jstring
145 !s <- jstring
114 return s
146 return s
147+{-# INLINE object_' #-}
115
148
149+objectValues :: ([(Text, Value)] -> Either String Object)
116−objectValues :: Parser Text -> Parser Value -> Parser (H.HashMap Text Value)
150+ -> Parser Text -> Parser Value -> Parser (H.HashMap Text Value)
117−objectValues str val = do
151+objectValues mkObject str val = do
118 skipSpace
152 skipSpace
119 w <- A.peekWord8'
153 w <- A.peekWord8'
120 if w == CLOSE_CURLY
154 if w == CLOSE_CURLY
@@ -124,22 +158,26 @@ objectValues str val = do
124 -- Why use acc pattern here, you may ask? because 'H.fromList' use 'unsafeInsert'
158 -- Why use acc pattern here, you may ask? because 'H.fromList' use 'unsafeInsert'
125 -- and it's much faster because it's doing in place update to the 'HashMap'!
159 -- and it's much faster because it's doing in place update to the 'HashMap'!
126 loop acc = do
160 loop acc = do
127− k <- str <* skipSpace <* char ':'
161+ k <- (str A.<?> "object key") <* skipSpace <* (char ':' A.<?> "':'")
128− v <- val <* skipSpace
162+ v <- (val A.<?> "object value") <* skipSpace
129− ch <- A.satisfy $ \w -> w == COMMA || w == CLOSE_CURLY
163+ ch <- A.satisfy (\w -> w == COMMA || w == CLOSE_CURLY) A.<?> "',' or '}'"
130 let acc' = (k, v) : acc
164 let acc' = (k, v) : acc
131 if ch == COMMA
165 if ch == COMMA
132 then skipSpace >> loop acc'
166 then skipSpace >> loop acc'
133− else return (H.fromList acc')
167+ else case mkObject acc' of
168+ Left err -> fail err
169+ Right obj -> pure obj
134{-# INLINE objectValues #-}
170{-# INLINE objectValues #-}
135
171
136−array_ :: Parser Value
172+array_ :: Parser Value -> Parser Value
137−array_ = {-# SCC "array_" #-} Array <$> arrayValues value
173+array_ val = {-# SCC "array_" #-} Array <$> arrayValues val
174+{-# INLINE array_ #-}
138
175
139−array_' :: Parser Value
176+array_' :: Parser Value -> Parser Value
140−array_' = {-# SCC "array_'" #-} do
177+array_' val = {-# SCC "array_'" #-} do
141− !vals <- arrayValues value'
178+ !vals <- arrayValues val
142 return (Array vals)
179 return (Array vals)
180+{-# INLINE array_' #-}
143
181
144arrayValues :: Parser Value -> Parser (Vector Value)
182arrayValues :: Parser Value -> Parser (Vector Value)
145arrayValues val = do
183arrayValues val = do
@@ -150,57 +188,133 @@ arrayValues val = do
150 else loop [] 1
188 else loop [] 1
151 where
189 where
152 loop acc !len = do
190 loop acc !len = do
153− v <- val <* skipSpace
191+ v <- (val A.<?> "json list value") <* skipSpace
154− ch <- A.satisfy $ \w -> w == COMMA || w == CLOSE_SQUARE
192+ ch <- A.satisfy (\w -> w == COMMA || w == CLOSE_SQUARE) A.<?> "',' or ']'"
155 if ch == COMMA
193 if ch == COMMA
156 then skipSpace >> loop (v:acc) (len+1)
194 then skipSpace >> loop (v:acc) (len+1)
157 else return (Vector.reverse (Vector.fromListN len (v:acc)))
195 else return (Vector.reverse (Vector.fromListN len (v:acc)))
158{-# INLINE arrayValues #-}
196{-# INLINE arrayValues #-}
159
197
160−-- | Parse any JSON value. You should usually 'json' in preference to
198+-- | Parse any JSON value. Synonym of 'json'.
161−-- this function, as this function relaxes the object-or-array
162−-- requirement of RFC 4627.
163−--
164−-- In particular, be careful in using this function if you think your
165−-- code might interoperate with Javascript. A na&#xef;ve Javascript
166−-- library that parses JSON data using @eval@ is vulnerable to attack
167−-- unless the encoded data represents an object or an array. JSON
168−-- implementations in other languages conform to that same restriction
169−-- to preserve interoperability and security.
170value :: Parser Value
199value :: Parser Value
171−value = do
200+value = jsonWith (pure . H.fromList)
201+
202+-- | Parse any JSON value.
203+--
204+-- This parser is parameterized by a function to construct an 'Object'
205+-- from a raw list of key-value pairs, where duplicates are preserved.
206+-- The pairs appear in __reverse order__ from the source.
207+--
208+-- ==== __Examples__
209+--
210+-- 'json' keeps only the first occurence of each key, using 'HashMap.Lazy.fromList'.
211+--
212+-- @
213+-- 'json' = 'jsonWith' ('Right' '.' 'H.fromList')
214+-- @
215+--
216+-- 'jsonLast' keeps the last occurence of each key, using
217+-- @'HashMap.Lazy.fromListWith' ('const' 'id')@.
218+--
219+-- @
220+-- 'jsonLast' = 'jsonWith' ('Right' '.' 'HashMap.Lazy.fromListWith' ('const' 'id'))
221+-- @
222+--
223+-- 'jsonAccum' keeps wraps all values in arrays to keep duplicates, using
224+-- 'fromListAccum'.
225+--
226+-- @
227+-- 'jsonAccum' = 'jsonWith' ('Right' . 'fromListAccum')
228+-- @
229+--
230+-- 'jsonNoDup' fails if any object contains duplicate keys, using 'parseListNoDup'.
231+--
232+-- @
233+-- 'jsonNoDup' = 'jsonWith' 'parseListNoDup'
234+-- @
235+jsonWith :: ([(Text, Value)] -> Either String Object) -> Parser Value
236+jsonWith mkObject = fix $ \value_ -> do
172 skipSpace
237 skipSpace
173 w <- A.peekWord8'
238 w <- A.peekWord8'
174 case w of
239 case w of
175 DOUBLE_QUOTE -> A.anyWord8 *> (String <$> jstring_)
240 DOUBLE_QUOTE -> A.anyWord8 *> (String <$> jstring_)
176− OPEN_CURLY -> A.anyWord8 *> object_
241+ OPEN_CURLY -> A.anyWord8 *> object_ mkObject value_
177− OPEN_SQUARE -> A.anyWord8 *> array_
242+ OPEN_SQUARE -> A.anyWord8 *> array_ value_
178− C_f -> string "false" *> pure (Bool False)
243+ C_f -> string "false" $> Bool False
179− C_t -> string "true" *> pure (Bool True)
244+ C_t -> string "true" $> Bool True
180− C_n -> string "null" *> pure Null
245+ C_n -> string "null" $> Null
181 _ | w >= 48 && w <= 57 || w == 45
246 _ | w >= 48 && w <= 57 || w == 45
182 -> Number <$> scientific
247 -> Number <$> scientific
183 | otherwise -> fail "not a valid json value"
248 | otherwise -> fail "not a valid json value"
249+{-# INLINE jsonWith #-}
250+
251+-- | Variant of 'json' which keeps only the last occurence of every key.
252+jsonLast :: Parser Value
253+jsonLast = jsonWith (Right . H.fromListWith (const id))
184
254
255+-- | Variant of 'json' wrapping all object mappings in 'Array' to preserve
256+-- key-value pairs with the same keys.
257+jsonAccum :: Parser Value
258+jsonAccum = jsonWith (Right . fromListAccum)
259+
260+-- | Variant of 'json' which fails if any object contains duplicate keys.
261+jsonNoDup :: Parser Value
262+jsonNoDup = jsonWith parseListNoDup
263+
264+-- | @'fromListAccum' kvs@ is an object mapping keys to arrays containing all
265+-- associated values from the original list @kvs@.
266+--
267+-- >>> fromListAccum [("apple", Bool True), ("apple", Bool False), ("orange", Bool False)]
268+-- fromList [("apple", [Bool False, Bool True]), ("orange", [Bool False])]
269+fromListAccum :: [(Text, Value)] -> Object
270+fromListAccum =
271+ fmap (Array . Vector.fromList . ($ [])) . H.fromListWith (.) . (fmap . fmap) (:)
272+
273+-- | @'fromListNoDup' kvs@ fails if @kvs@ contains duplicate keys.
274+parseListNoDup :: [(Text, Value)] -> Either String Object
275+parseListNoDup =
276+ H.traverseWithKey unwrap . H.fromListWith (\_ _ -> Nothing) . (fmap . fmap) Just
277+ where
278+ unwrap k Nothing = Left $ "found duplicate key: " ++ show k
279+ unwrap _ (Just v) = Right v
280+
185−-- | Strict version of 'value'. See also 'json''.
281+-- | Strict version of 'value'. Synonym of 'json''.
186value' :: Parser Value
282value' :: Parser Value
187−value' = do
283+value' = jsonWith' (pure . H.fromList)
284+
285+-- | Strict version of 'jsonWith'.
286+jsonWith' :: ([(Text, Value)] -> Either String Object) -> Parser Value
287+jsonWith' mkObject = fix $ \value_ -> do
188 skipSpace
288 skipSpace
189 w <- A.peekWord8'
289 w <- A.peekWord8'
190 case w of
290 case w of
191 DOUBLE_QUOTE -> do
291 DOUBLE_QUOTE -> do
192 !s <- A.anyWord8 *> jstring_
292 !s <- A.anyWord8 *> jstring_
193 return (String s)
293 return (String s)
194− OPEN_CURLY -> A.anyWord8 *> object_'
294+ OPEN_CURLY -> A.anyWord8 *> object_' mkObject value_
195− OPEN_SQUARE -> A.anyWord8 *> array_'
295+ OPEN_SQUARE -> A.anyWord8 *> array_' value_
196− C_f -> string "false" *> pure (Bool False)
296+ C_f -> string "false" $> Bool False
197− C_t -> string "true" *> pure (Bool True)
297+ C_t -> string "true" $> Bool True
198− C_n -> string "null" *> pure Null
298+ C_n -> string "null" $> Null
199 _ | w >= 48 && w <= 57 || w == 45
299 _ | w >= 48 && w <= 57 || w == 45
200 -> do
300 -> do
201 !n <- scientific
301 !n <- scientific
202 return (Number n)
302 return (Number n)
203 | otherwise -> fail "not a valid json value"
303 | otherwise -> fail "not a valid json value"
304+{-# INLINE jsonWith' #-}
305+
306+-- | Variant of 'json'' which keeps only the last occurence of every key.
307+jsonLast' :: Parser Value
308+jsonLast' = jsonWith' (pure . H.fromListWith (const id))
309+
310+-- | Variant of 'json'' wrapping all object mappings in 'Array' to preserve
311+-- key-value pairs with the same keys.
312+jsonAccum' :: Parser Value
313+jsonAccum' = jsonWith' (pure . fromListAccum)
314+
315+-- | Variant of 'json'' which fails if any object contains duplicate keys.
316+jsonNoDup' :: Parser Value
317+jsonNoDup' = jsonWith' parseListNoDup
204
318
205-- | Parse a quoted JSON string.
319-- | Parse a quoted JSON string.
206jstring :: Parser Text
320jstring :: Parser Text
@@ -276,7 +390,12 @@ eitherDecodeWith p to s =
276 L.Done _ v -> case to v of
390 L.Done _ v -> case to v of
277 ISuccess a -> Right a
391 ISuccess a -> Right a
278 IError path msg -> Left (path, msg)
392 IError path msg -> Left (path, msg)
279− L.Fail _ _ msg -> Left ([], msg)
393+ L.Fail _ ctx msg -> Left ([], buildMsg ctx msg)
394+ where
395+ buildMsg :: [String] -> String -> String
396+ buildMsg [] msg = msg
397+ buildMsg (expectation:_) msg =
398+ msg ++ ". Expecting " ++ expectation
280{-# INLINE eitherDecodeWith #-}
399{-# INLINE eitherDecodeWith #-}
281
400
282eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
401eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
@@ -329,12 +448,11 @@ data SP = SP !Integer {-# UNPACK #-}!Int
329
448
330decimal0 :: Parser Integer
449decimal0 :: Parser Integer
331decimal0 = do
450decimal0 = do
332− let step a w = a * 10 + fromIntegral (w - zero)
333− zero = 48
451+ let zero = 48
334 digits <- A.takeWhile1 isDigit_w8
452 digits <- A.takeWhile1 isDigit_w8
335 if B.length digits > 1 && B.unsafeHead digits == zero
453 if B.length digits > 1 && B.unsafeHead digits == zero
336 then fail "leading zero"
454 then fail "leading zero"
337− else return (B.foldl' step 0 digits)
455+ else return (bsToInteger digits)
338
456
339-- | Parse a JSON number.
457-- | Parse a JSON number.
340scientific :: Parser Scientific
458scientific :: Parser Scientific
@@ -367,3 +485,53 @@ scientific = do
367 fmap (Sci.scientific signedCoeff . (e +)) (signed decimal)) <|>
485 fmap (Sci.scientific signedCoeff . (e +)) (signed decimal)) <|>
368 return (Sci.scientific signedCoeff e)
486 return (Sci.scientific signedCoeff e)
369{-# INLINE scientific #-}
487{-# INLINE scientific #-}
488+
489+------------------ Copy-pasted and adapted from base ------------------------
490+
491+bsToInteger :: B.ByteString -> Integer
492+bsToInteger bs
493+ | l > 40 = valInteger 10 l [ fromIntegral (w - 48) | w <- B.unpack bs ]
494+ | otherwise = bsToIntegerSimple bs
495+ where
496+ l = B.length bs
497+
498+bsToIntegerSimple :: B.ByteString -> Integer
499+bsToIntegerSimple = B.foldl' step 0 where
500+ step a b = a * 10 + fromIntegral (b - 48) -- 48 = '0'
501+
502+-- A sub-quadratic algorithm for Integer. Pairs of adjacent radix b
503+-- digits are combined into a single radix b^2 digit. This process is
504+-- repeated until we are left with a single digit. This algorithm
505+-- performs well only on large inputs, so we use the simple algorithm
506+-- for smaller inputs.
507+valInteger :: Integer -> Int -> [Integer] -> Integer
508+valInteger = go
509+ where
510+ go :: Integer -> Int -> [Integer] -> Integer
511+ go _ _ [] = 0
512+ go _ _ [d] = d
513+ go b l ds
514+ | l > 40 = b' `seq` go b' l' (combine b ds')
515+ | otherwise = valSimple b ds
516+ where
517+ -- ensure that we have an even number of digits
518+ -- before we call combine:
519+ ds' = if even l then ds else 0 : ds
520+ b' = b * b
521+ l' = (l + 1) `quot` 2
522+
523+ combine b (d1 : d2 : ds) = d `seq` (d : combine b ds)
524+ where
525+ d = d1 * b + d2
526+ combine _ [] = []
527+ combine _ [_] = errorWithoutStackTrace "this should not happen"
528+
529+-- The following algorithm is only linear for types whose Num operations
530+-- are in constant time.
531+valSimple :: Integer -> [Integer] -> Integer
532+valSimple base = go 0
533+ where
534+ go r [] = r
535+ go r (d : ds) = r' `seq` go r' ds
536+ where
537+ r' = r * base + fromIntegral d
Data/Aeson/Parser/Time.hs+2−1
@@ -1,3 +1,5 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
1module Data.Aeson.Parser.Time
3module Data.Aeson.Parser.Time
2 (
4 (
3 run
5 run
@@ -9,7 +11,6 @@ module Data.Aeson.Parser.Time
9 , zonedTime
11 , zonedTime
10 ) where
12 ) where
11
13
12−import Prelude ()
13import Prelude.Compat
14import Prelude.Compat
14
15
15import Data.Attoparsec.Text (Parser)
16import Data.Attoparsec.Text (Parser)
Data/Aeson/QQ/Simple.hsnew file+25
@@ -0,0 +1,25 @@
1+-- | Like "Data.Aeson.QQ" but without interpolation.
2+module Data.Aeson.QQ.Simple (aesonQQ) where
3+
4+import Data.Aeson
5+import qualified Data.Text as T
6+import qualified Data.Text.Encoding as TE
7+import Language.Haskell.TH
8+import Language.Haskell.TH.Quote
9+import Language.Haskell.TH.Syntax (Lift (..))
10+import Prelude ()
11+import Prelude.Compat
12+
13+aesonQQ :: QuasiQuoter
14+aesonQQ = QuasiQuoter
15+ { quoteExp = aesonExp
16+ , quotePat = const $ error "No quotePat defined for jsonQQ"
17+ , quoteType = const $ error "No quoteType defined for jsonQQ"
18+ , quoteDec = const $ error "No quoteDec defined for jsonQQ"
19+ }
20+
21+aesonExp :: String -> ExpQ
22+aesonExp txt =
23+ case eitherDecodeStrict $ TE.encodeUtf8 $ T.pack txt of
24+ Left err -> error $ "Error in aesonExp: " ++ show err
25+ Right val -> lift (val :: Value)
Data/Aeson/TH.hs+248−363
@@ -85,7 +85,6 @@ $('deriveJSON' 'defaultOptions' ''(,,,))
85@
85@
86
86
87-}
87-}
88−
89module Data.Aeson.TH
88module Data.Aeson.TH
90 (
89 (
91 -- * Encoding configuration
90 -- * Encoding configuration
@@ -117,49 +116,42 @@ module Data.Aeson.TH
117 , mkLiftParseJSON2
116 , mkLiftParseJSON2
118 ) where
117 ) where
119
118
120−import Prelude ()
119+import Prelude.Compat
121−import Prelude.Compat hiding (exp)
122
120
123import Control.Applicative ((<|>))
121import Control.Applicative ((<|>))
124−import Data.Aeson (Object, (.=), (.:), FromJSON(..), FromJSON1(..), FromJSON2(..), ToJSON(..), ToJSON1(..), ToJSON2(..))
122+import Data.Aeson (Object, (.:), FromJSON(..), FromJSON1(..), FromJSON2(..), ToJSON(..), ToJSON1(..), ToJSON2(..))
125import Data.Aeson.Types (Options(..), Parser, SumEncoding(..), Value(..), defaultOptions, defaultTaggedObject)
123import Data.Aeson.Types (Options(..), Parser, SumEncoding(..), Value(..), defaultOptions, defaultTaggedObject)
126−import Data.Aeson.Types.Internal ((<?>), Pair, JSONPathElement(Key))
124+import Data.Aeson.Types.Internal ((<?>), JSONPathElement(Key))
127import Data.Aeson.Types.FromJSON (parseOptionalFieldWith)
125import Data.Aeson.Types.FromJSON (parseOptionalFieldWith)
126+import Data.Aeson.Types.ToJSON (fromPairs, pair)
128import Control.Monad (liftM2, unless, when)
127import Control.Monad (liftM2, unless, when)
129import Data.Foldable (foldr')
128import Data.Foldable (foldr')
130#if MIN_VERSION_template_haskell(2,8,0) && !MIN_VERSION_template_haskell(2,10,0)
129#if MIN_VERSION_template_haskell(2,8,0) && !MIN_VERSION_template_haskell(2,10,0)
131import Data.List (nub)
130import Data.List (nub)
132#endif
131#endif
133−import Data.List (foldl', genericLength , intercalate , intersperse, partition, union)
132+import Data.List (foldl', genericLength, intercalate, partition, union)
134import Data.List.NonEmpty ((<|), NonEmpty((:|)))
133import Data.List.NonEmpty ((<|), NonEmpty((:|)))
135import Data.Map (Map)
134import Data.Map (Map)
136import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
135import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
136+import qualified Data.Monoid as Monoid
137import Data.Set (Set)
137import Data.Set (Set)
138−#if MIN_VERSION_template_haskell(2,8,0)
139import Language.Haskell.TH hiding (Arity)
138import Language.Haskell.TH hiding (Arity)
140−#else
141−import Language.Haskell.TH
142−#endif
143import Language.Haskell.TH.Datatype
139import Language.Haskell.TH.Datatype
144−#if MIN_VERSION_template_haskell(2,7,0) && !(MIN_VERSION_template_haskell(2,8,0))
145−import Language.Haskell.TH.Lib (starK)
146−#endif
147#if MIN_VERSION_template_haskell(2,8,0) && !(MIN_VERSION_template_haskell(2,10,0))
140#if MIN_VERSION_template_haskell(2,8,0) && !(MIN_VERSION_template_haskell(2,10,0))
148import Language.Haskell.TH.Syntax (mkNameG_tc)
141import Language.Haskell.TH.Syntax (mkNameG_tc)
149#endif
142#endif
150import Text.Printf (printf)
143import Text.Printf (printf)
151−import qualified Data.Aeson as A
152import qualified Data.Aeson.Encoding.Internal as E
144import qualified Data.Aeson.Encoding.Internal as E
153import qualified Data.Foldable as F (all)
145import qualified Data.Foldable as F (all)
154−import qualified Data.HashMap.Strict as H (lookup, toList)
146+import qualified Data.HashMap.Strict as H (difference, fromList, keys, lookup, toList)
155import qualified Data.List.NonEmpty as NE (length, reverse)
147import qualified Data.List.NonEmpty as NE (length, reverse)
156import qualified Data.Map as M (fromList, keys, lookup , singleton, size)
148import qualified Data.Map as M (fromList, keys, lookup , singleton, size)
149+import qualified Data.Semigroup as Semigroup (Option(..))
157import qualified Data.Set as Set (empty, insert, member)
150import qualified Data.Set as Set (empty, insert, member)
158import qualified Data.Text as T (Text, pack, unpack)
151import qualified Data.Text as T (Text, pack, unpack)
159−import qualified Data.Vector as V (unsafeIndex, null, length, create, fromList)
152+import qualified Data.Vector as V (unsafeIndex, null, length, create, empty)
160import qualified Data.Vector.Mutable as VM (unsafeNew, unsafeWrite)
153import qualified Data.Vector.Mutable as VM (unsafeNew, unsafeWrite)
161
154
162−
163--------------------------------------------------------------------------------
155--------------------------------------------------------------------------------
164-- Convenience
156-- Convenience
165--------------------------------------------------------------------------------
157--------------------------------------------------------------------------------
@@ -253,8 +245,8 @@ deriveToJSONCommon :: JSONClass
253 -> Name
245 -> Name
254 -- ^ Name of the type for which to generate an instance.
246 -- ^ Name of the type for which to generate an instance.
255 -> Q [Dec]
247 -> Q [Dec]
256−deriveToJSONCommon = deriveJSONClass [ (ToJSON, \jc _ -> consToValue jc)
248+deriveToJSONCommon = deriveJSONClass [ (ToJSON, \jc _ -> consToValue Value jc)
257− , (ToEncoding, \jc _ -> consToEncoding jc)
249+ , (ToEncoding, \jc _ -> consToValue Encoding jc)
258 ]
250 ]
259
251
260-- | Generates a lambda expression which encodes the given data type or
252-- | Generates a lambda expression which encodes the given data type or
@@ -284,7 +276,7 @@ mkToJSONCommon :: JSONClass -- ^ Which class's method is being derived.
284 -> Options -- ^ Encoding options.
276 -> Options -- ^ Encoding options.
285 -> Name -- ^ Name of the encoded type.
277 -> Name -- ^ Name of the encoded type.
286 -> Q Exp
278 -> Q Exp
287−mkToJSONCommon = mkFunCommon (\jc _ -> consToValue jc)
279+mkToJSONCommon = mkFunCommon (\jc _ -> consToValue Value jc)
288
280
289-- | Generates a lambda expression which encodes the given data type or
281-- | Generates a lambda expression which encodes the given data type or
290-- data family instance constructor as a JSON string.
282-- data family instance constructor as a JSON string.
@@ -313,12 +305,14 @@ mkToEncodingCommon :: JSONClass -- ^ Which class's method is being derived.
313 -> Options -- ^ Encoding options.
305 -> Options -- ^ Encoding options.
314 -> Name -- ^ Name of the encoded type.
306 -> Name -- ^ Name of the encoded type.
315 -> Q Exp
307 -> Q Exp
316−mkToEncodingCommon = mkFunCommon (\jc _ -> consToEncoding jc)
308+mkToEncodingCommon = mkFunCommon (\jc _ -> consToValue Encoding jc)
317
309
318-- | Helper function used by both 'deriveToJSON' and 'mkToJSON'. Generates
310-- | Helper function used by both 'deriveToJSON' and 'mkToJSON'. Generates
319−-- code to generate a 'Value' of a number of constructors. All constructors
311+-- code to generate a 'Value' or 'Encoding' of a number of constructors. All
320−-- must be from the same type.
312+-- constructors must be from the same type.
313+consToValue :: ToJSONFun
314+ -- ^ The method ('toJSON' or 'toEncoding') being derived.
321−consToValue :: JSONClass
315+ -> JSONClass
322 -- ^ The ToJSON variant being derived.
316 -- ^ The ToJSON variant being derived.
323 -> Options
317 -> Options
324 -- ^ Encoding options.
318 -- ^ Encoding options.
@@ -328,16 +322,16 @@ consToValue :: JSONClass
328 -- ^ Constructors for which to generate JSON generating code.
322 -- ^ Constructors for which to generate JSON generating code.
329 -> Q Exp
323 -> Q Exp
330
324
331−consToValue _ _ _ [] = error $ "Data.Aeson.TH.consToValue: "
325+consToValue _ _ _ _ [] = error $ "Data.Aeson.TH.consToValue: "
332 ++ "Not a single constructor given!"
326 ++ "Not a single constructor given!"
333
327
334−consToValue jc opts vars cons = do
328+consToValue target jc opts instTys cons = do
335 value <- newName "value"
329 value <- newName "value"
336 tjs <- newNameList "_tj" $ arityInt jc
330 tjs <- newNameList "_tj" $ arityInt jc
337 tjls <- newNameList "_tjl" $ arityInt jc
331 tjls <- newNameList "_tjl" $ arityInt jc
338 let zippedTJs = zip tjs tjls
332 let zippedTJs = zip tjs tjls
339 interleavedTJs = interleave tjs tjls
333 interleavedTJs = interleave tjs tjls
340− lastTyVars = map varTToName $ drop (length vars - arityInt jc) vars
334+ lastTyVars = map varTToName $ drop (length instTys - arityInt jc) instTys
341 tvMap = M.fromList $ zip lastTyVars zippedTJs
335 tvMap = M.fromList $ zip lastTyVars zippedTJs
342 lamE (map varP $ interleavedTJs ++ [value]) $
336 lamE (map varP $ interleavedTJs ++ [value]) $
343 caseE (varE value) (matches tvMap)
337 caseE (varE value) (matches tvMap)
@@ -345,66 +339,26 @@ consToValue jc opts vars cons = do
345 matches tvMap = case cons of
339 matches tvMap = case cons of
346 -- A single constructor is directly encoded. The constructor itself may be
340 -- A single constructor is directly encoded. The constructor itself may be
347 -- forgotten.
341 -- forgotten.
348− [con] | not (tagSingleConstructors opts) -> [argsToValue jc tvMap opts False con]
342+ [con] | not (tagSingleConstructors opts) -> [argsToValue target jc tvMap opts False con]
349 _ | allNullaryToStringTag opts && all isNullary cons ->
343 _ | allNullaryToStringTag opts && all isNullary cons ->
350− [ match (conP conName []) (normalB $ conStr opts conName) []
344+ [ match (conP conName []) (normalB $ conStr target opts conName) []
351 | con <- cons
345 | con <- cons
352 , let conName = constructorName con
346 , let conName = constructorName con
353 ]
347 ]
354− | otherwise -> [argsToValue jc tvMap opts True con | con <- cons]
348+ | otherwise -> [argsToValue target jc tvMap opts True con | con <- cons]
355
349
350+-- | Name of the constructor as a quoted 'Value' or 'Encoding'.
356−conStr :: Options -> Name -> Q Exp
351+conStr :: ToJSONFun -> Options -> Name -> Q Exp
357−conStr opts = appE [|String|] . conTxt opts
352+conStr Value opts = appE [|String|] . conTxt opts
353+conStr Encoding opts = appE [|E.text|] . conTxt opts
358
354
355+-- | Name of the constructor as a quoted 'Text'.
359conTxt :: Options -> Name -> Q Exp
356conTxt :: Options -> Name -> Q Exp
360−conTxt opts = appE [|T.pack|] . conStringE opts
357+conTxt opts = appE [|T.pack|] . stringE . conString opts
361−
362−conStringE :: Options -> Name -> Q Exp
363−conStringE opts = stringE . constructorTagModifier opts . nameBase
364−
365−-- | Helper function used by both 'deriveToJSON' and 'mkToEncoding'. Generates
366−-- code to write out a value for a number of constructors. All constructors
367−-- must be from the same type.
368−consToEncoding :: JSONClass
369− -- ^ The ToJSON variant being derived.
370− -> Options
371− -- ^ Encoding options.
372− -> [Type]
373− -- ^ The types from the data type/data family instance declaration
374− -> [ConstructorInfo]
375− -- ^ Constructors for which to generate JSON generating code.
376− -> Q Exp
377−
378−consToEncoding _ _ _ [] = error $ "Data.Aeson.TH.consToEncoding: "
379− ++ "Not a single constructor given!"
380
358
381−consToEncoding jc opts vars cons = do
382− value <- newName "value"
383− tes <- newNameList "_te" $ arityInt jc
384− tels <- newNameList "_tel" $ arityInt jc
385− let zippedTEs = zip tes tels
386− interleavedTEs = interleave tes tels
387− lastTyVars = map varTToName $ drop (length vars - arityInt jc) vars
388− tvMap = M.fromList $ zip lastTyVars zippedTEs
389− lamE (map varP $ interleavedTEs ++ [value]) $
390− caseE (varE value) (matches tvMap)
391− where
392− matches tvMap = case cons of
393− -- A single constructor is directly encoded. The constructor itself may be
394− -- forgotten.
395− [con] | not (tagSingleConstructors opts) -> [argsToEncoding jc tvMap opts False con]
396− -- Encode just the name of the constructor of a sum type iff all the
359+-- | Name of the constructor.
397− -- constructors are nullary.
398− _ | allNullaryToStringTag opts && all isNullary cons ->
399− [ match (conP conName [])
400− (normalB $ encStr opts conName) []
401− | con <- cons
402− , let conName = constructorName con
403− ]
404− | otherwise -> [argsToEncoding jc tvMap opts True con | con <- cons]
405−
406−encStr :: Options -> Name -> Q Exp
360+conString :: Options -> Name -> String
407−encStr opts = appE [|E.text|] . conTxt opts
361+conString opts = constructorTagModifier opts . nameBase
408
362
409-- | If constructor is nullary.
363-- | If constructor is nullary.
410isNullary :: ConstructorInfo -> Bool
364isNullary :: ConstructorInfo -> Bool
@@ -412,127 +366,133 @@ isNullary ConstructorInfo { constructorVariant = NormalConstructor
412 , constructorFields = tys } = null tys
366 , constructorFields = tys } = null tys
413isNullary _ = False
367isNullary _ = False
414
368
369+-- | Wrap fields of a non-record constructor. See 'sumToValue'.
415−sumToValue :: Options -> Bool -> Bool -> Name -> Q Exp -> Q Exp
370+opaqueSumToValue :: ToJSONFun -> Options -> Bool -> Bool -> Name -> ExpQ -> ExpQ
371+opaqueSumToValue target opts multiCons nullary conName value =
372+ sumToValue target opts multiCons nullary conName
373+ value
374+ pairs
375+ where
376+ pairs contentsFieldName = pairE contentsFieldName value
377+
378+-- | Wrap fields of a record constructor. See 'sumToValue'.
379+recordSumToValue :: ToJSONFun -> Options -> Bool -> Bool -> Name -> ExpQ -> ExpQ
380+recordSumToValue target opts multiCons nullary conName pairs =
416−sumToValue opts multiCons nullary conName exp
381+ sumToValue target opts multiCons nullary conName
382+ (fromPairsE pairs)
383+ (const pairs)
384+
385+-- | Wrap fields of a constructor.
386+sumToValue
387+ :: ToJSONFun
388+ -- ^ The method being derived.
389+ -> Options
390+ -- ^ Deriving options.
391+ -> Bool
392+ -- ^ Does this type have multiple constructors.
393+ -> Bool
394+ -- ^ Is this constructor nullary.
395+ -> Name
396+ -- ^ Constructor name.
397+ -> ExpQ
398+ -- ^ Fields of the constructor as a 'Value' or 'Encoding'.
399+ -> (String -> ExpQ)
400+ -- ^ Representation of an 'Object' fragment used for the 'TaggedObject'
401+ -- variant; of type @[(Text,Value)]@ or @[Encoding]@, depending on the method
402+ -- being derived.
403+ --
404+ -- - For non-records, produces a pair @"contentsFieldName":value@,
405+ -- given a @contentsFieldName@ as an argument. See 'opaqueSumToValue'.
406+ -- - For records, produces the list of pairs corresponding to fields of the
407+ -- encoded value (ignores the argument). See 'recordSumToValue'.
408+ -> ExpQ
409+sumToValue target opts multiCons nullary conName value pairs
417 | multiCons =
410 | multiCons =
418 case sumEncoding opts of
411 case sumEncoding opts of
419 TwoElemArray ->
412 TwoElemArray ->
420− [|Array|] `appE` ([|V.fromList|] `appE` listE [conStr opts conName, exp])
413+ array target [conStr target opts conName, value]
421 TaggedObject{tagFieldName, contentsFieldName} ->
414 TaggedObject{tagFieldName, contentsFieldName} ->
415+ -- TODO: Maybe throw an error in case
416+ -- tagFieldName overwrites a field in pairs.
422− let tag = infixApp [|T.pack tagFieldName|] [|(.=)|] (conStr opts conName)
417+ let tag = pairE tagFieldName (conStr target opts conName)
423− contents = infixApp [|T.pack contentsFieldName|] [|(.=)|] exp
418+ content = pairs contentsFieldName
424− in
419+ in fromPairsE $
425− [|A.object|] `appE` listE (if nullary then [tag] else [tag, contents])
420+ if nullary then tag else infixApp tag [|(Monoid.<>)|] content
426 ObjectWithSingleField ->
421 ObjectWithSingleField ->
427− [|A.object|] `appE` listE
422+ objectE [(conString opts conName, value)]
428− [ infixApp (conTxt opts conName) [|(.=)|] exp
429− ]
430− UntaggedValue | nullary -> conStr opts conName
423+ UntaggedValue | nullary -> conStr target opts conName
431− UntaggedValue -> exp
424+ UntaggedValue -> value
432− | otherwise = exp
425+ | otherwise = value
433
426
434-- | Generates code to generate the JSON encoding of a single constructor.
427-- | Generates code to generate the JSON encoding of a single constructor.
435−argsToValue :: JSONClass -> TyVarMap -> Options -> Bool -> ConstructorInfo -> Q Match
428+argsToValue :: ToJSONFun -> JSONClass -> TyVarMap -> Options -> Bool -> ConstructorInfo -> Q Match
436
429
437-- Polyadic constructors with special case for unary constructors.
430-- Polyadic constructors with special case for unary constructors.
438−argsToValue jc tvMap opts multiCons
431+argsToValue target jc tvMap opts multiCons
439 ConstructorInfo { constructorName = conName
432 ConstructorInfo { constructorName = conName
440 , constructorVariant = NormalConstructor
433 , constructorVariant = NormalConstructor
441 , constructorFields = argTys } = do
434 , constructorFields = argTys } = do
442 argTys' <- mapM resolveTypeSynonyms argTys
435 argTys' <- mapM resolveTypeSynonyms argTys
443 let len = length argTys'
436 let len = length argTys'
444 args <- newNameList "arg" len
437 args <- newNameList "arg" len
445− js <- case [ dispatchToJSON jc conName tvMap argTy
438+ let js = case [ dispatchToJSON target jc conName tvMap argTy
446− `appE` varE arg
439+ `appE` varE arg
447− | (arg, argTy) <- zip args argTys'
440+ | (arg, argTy) <- zip args argTys'
448− ] of
441+ ] of
449− -- Single argument is directly converted.
442+ -- Single argument is directly converted.
450− [e] -> return e
443+ [e] -> e
451− -- Multiple arguments are converted to a JSON array.
444+ -- Zero and multiple arguments are converted to a JSON array.
452− es -> do
445+ es -> array target es
453− mv <- newName "mv"
446+
454− let newMV = bindS (varP mv)
455− ([|VM.unsafeNew|] `appE`
456− litE (integerL $ fromIntegral len))
457− stmts = [ noBindS $
458− [|VM.unsafeWrite|] `appE`
459− varE mv `appE`
460− litE (integerL ix) `appE`
461− e
462− | (ix, e) <- zip [(0::Integer)..] es
463− ]
464− ret = noBindS $ [|return|] `appE` varE mv
465− return $ [|Array|] `appE`
466− (varE 'V.create `appE`
467− doE (newMV:stmts++[ret]))
468 match (conP conName $ map varP args)
447 match (conP conName $ map varP args)
469− (normalB $ sumToValue opts multiCons (null argTys') conName js)
448+ (normalB $ opaqueSumToValue target opts multiCons (null argTys') conName js)
470 []
449 []
471
450
472-- Records.
451-- Records.
473−argsToValue jc tvMap opts multiCons
452+argsToValue target jc tvMap opts multiCons
474 info@ConstructorInfo { constructorName = conName
453 info@ConstructorInfo { constructorName = conName
475 , constructorVariant = RecordConstructor fields
454 , constructorVariant = RecordConstructor fields
476 , constructorFields = argTys } =
455 , constructorFields = argTys } =
477 case (unwrapUnaryRecords opts, not multiCons, argTys) of
456 case (unwrapUnaryRecords opts, not multiCons, argTys) of
478− (True,True,[_]) -> argsToValue jc tvMap opts multiCons
457+ (True,True,[_]) -> argsToValue target jc tvMap opts multiCons
479 (info{constructorVariant = NormalConstructor})
458 (info{constructorVariant = NormalConstructor})
480 _ -> do
459 _ -> do
481 argTys' <- mapM resolveTypeSynonyms argTys
460 argTys' <- mapM resolveTypeSynonyms argTys
482 args <- newNameList "arg" $ length argTys'
461 args <- newNameList "arg" $ length argTys'
483− let exp = [|A.object|] `appE` pairs
484−
485− pairs | omitNothingFields opts = infixApp maybeFields
462+ let pairs | omitNothingFields opts = infixApp maybeFields
486− [|(++)|]
463+ [|(Monoid.<>)|]
487 restFields
464 restFields
488− | otherwise = listE $ map toPair argCons
465+ | otherwise = mconcatE (map pureToPair argCons)
489
466
490− argCons = zip3 args argTys' fields
467+ argCons = zip3 (map varE args) argTys' fields
491
468
492− maybeFields = [|catMaybes|] `appE` listE (map maybeToPair maybes)
469+ maybeFields = mconcatE (map maybeToPair maybes)
493
470
494− restFields = listE $ map toPair rest
471+ restFields = mconcatE (map pureToPair rest)
495
472
496− (maybes, rest) = partition isMaybe argCons
473+ (maybes0, rest0) = partition isMaybe argCons
474+ (options, rest) = partition isOption rest0
475+ maybes = maybes0 ++ map optionToMaybe options
497
476
498− maybeToPair (arg, argTy, field) =
477+ maybeToPair = toPairLifted True
499− infixApp ([|keyValuePairWith|]
478+ pureToPair = toPairLifted False
500− `appE` dispatchToJSON jc conName tvMap argTy
501− `appE` toFieldName field)
502− [|(<$>)|]
503− (varE arg)
504
479
505− toPair (arg, argTy, field) =
480+ toPairLifted lifted (arg, argTy, field) =
506− [|keyValuePairWith|]
507− `appE` dispatchToJSON jc conName tvMap argTy
481+ let toValue = dispatchToJSON target jc conName tvMap argTy
508− `appE` toFieldName field
482+ fieldName = fieldLabel opts field
509− `appE` varE arg
483+ e arg' = pairE fieldName (toValue `appE` arg')
510−
484+ in if lifted
511− toFieldName field = [|T.pack|] `appE` fieldLabelExp opts field
485+ then do
486+ x <- newName "x"
487+ [|maybe mempty|] `appE` lam1E (varP x) (e (varE x)) `appE` arg
488+ else e arg
512
489
513 match (conP conName $ map varP args)
490 match (conP conName $ map varP args)
514− ( normalB
491+ (normalB $ recordSumToValue target opts multiCons (null argTys) conName pairs)
515− $ if multiCons
516− then case sumEncoding opts of
517− TwoElemArray -> [|toJSON|] `appE` tupE [conStr opts conName, exp]
518− TaggedObject{tagFieldName} ->
519− [|A.object|] `appE`
520− -- TODO: Maybe throw an error in case
521− -- tagFieldName overwrites a field in pairs.
522− infixApp (infixApp [|T.pack tagFieldName|]
523− [|(.=)|]
524− (conStr opts conName))
525− [|(:)|]
526− pairs
527− ObjectWithSingleField ->
528− [|A.object|] `appE` listE
529− [ infixApp (conTxt opts conName) [|(.=)|] exp ]
530− UntaggedValue -> exp
531− else exp
532− ) []
492+ []
533
493
534-- Infix constructors.
494-- Infix constructors.
535−argsToValue jc tvMap opts multiCons
495+argsToValue target jc tvMap opts multiCons
536 ConstructorInfo { constructorName = conName
496 ConstructorInfo { constructorName = conName
537 , constructorVariant = InfixConstructor
497 , constructorVariant = InfixConstructor
538 , constructorFields = argTys } = do
498 , constructorFields = argTys } = do
@@ -541,11 +501,12 @@ argsToValue jc tvMap opts multiCons
541 ar <- newName "argR"
501 ar <- newName "argR"
542 match (infixP (varP al) conName (varP ar))
502 match (infixP (varP al) conName (varP ar))
543 ( normalB
503 ( normalB
544− $ sumToValue opts multiCons False conName
504+ $ opaqueSumToValue target opts multiCons False conName
505+ $ array target
545− $ [|toJSON|] `appE` listE [ dispatchToJSON jc conName tvMap aTy
506+ [ dispatchToJSON target jc conName tvMap aTy
546− `appE` varE a
507+ `appE` varE a
547− | (a, aTy) <- [(al,alTy), (ar,arTy)]
508+ | (a, aTy) <- [(al,alTy), (ar,arTy)]
548− ]
509+ ]
549 )
510 )
550 []
511 []
551
512
@@ -553,151 +514,63 @@ isMaybe :: (a, Type, b) -> Bool
553isMaybe (_, AppT (ConT t) _, _) = t == ''Maybe
514isMaybe (_, AppT (ConT t) _, _) = t == ''Maybe
554isMaybe _ = False
515isMaybe _ = False
555
516
517+isOption :: (a, Type, b) -> Bool
518+isOption (_, AppT (ConT t) _, _) = t == ''Semigroup.Option
519+isOption _ = False
520+
521+optionToMaybe :: (ExpQ, b, c) -> (ExpQ, b, c)
522+optionToMaybe (a, b, c) = ([|Semigroup.getOption|] `appE` a, b, c)
523+
556(<^>) :: ExpQ -> ExpQ -> ExpQ
524(<^>) :: ExpQ -> ExpQ -> ExpQ
557(<^>) a b = infixApp a [|(E.><)|] b
525(<^>) a b = infixApp a [|(E.><)|] b
558infixr 6 <^>
526infixr 6 <^>
559
527
560−(<:>) :: ExpQ -> ExpQ -> ExpQ
561−(<:>) a b = a <^> [|E.colon|] <^> b
562−infixr 5 <:>
563−
564(<%>) :: ExpQ -> ExpQ -> ExpQ
528(<%>) :: ExpQ -> ExpQ -> ExpQ
565(<%>) a b = a <^> [|E.comma|] <^> b
529(<%>) a b = a <^> [|E.comma|] <^> b
566infixr 4 <%>
530infixr 4 <%>
567
531
568−array :: ExpQ -> ExpQ
532+-- | Wrap a list of quoted 'Value's in a quoted 'Array' (of type 'Value').
569−array exp = [|E.wrapArray|] `appE` exp
533+array :: ToJSONFun -> [ExpQ] -> ExpQ
570−
571−object :: ExpQ -> ExpQ
572−object exp = [|E.wrapObject|] `appE` exp
573−
574−sumToEncoding :: Options -> Bool -> Bool -> Name -> Q Exp -> Q Exp
575−sumToEncoding opts multiCons nullary conName exp
576− | multiCons =
577− let fexp = exp in
578− case sumEncoding opts of
579− TwoElemArray ->
580− array (encStr opts conName <%> fexp)
581− TaggedObject{tagFieldName, contentsFieldName} ->
582− let tag = [|E.text (T.pack tagFieldName)|] <:> encStr opts conName
583− contents = [|E.text (T.pack contentsFieldName)|] <:> fexp
584− in
585− object $
586− if nullary then tag else tag <%> contents
587− ObjectWithSingleField ->
588− object (encStr opts conName <:> fexp)
589− UntaggedValue | nullary -> encStr opts conName
590− UntaggedValue -> exp
591− | otherwise = exp
592−
593−-- | Generates code to generate the JSON encoding of a single constructor.
594−argsToEncoding :: JSONClass -> TyVarMap -> Options -> Bool -> ConstructorInfo -> Q Match
595−
596−-- Polyadic constructors with special case for unary constructors.
597−argsToEncoding jc tvMap opts multiCons
598− ConstructorInfo { constructorName = conName
599− , constructorVariant = NormalConstructor
600− , constructorFields = argTys } = do
601− argTys' <- mapM resolveTypeSynonyms argTys
602− args <- newNameList "arg" $ length argTys'
603− js <- case zip args argTys' of
604− -- Nullary constructors are converted to an empty array.
605− [] -> return [| E.emptyArray_ |]
534+array Encoding [] = [|E.emptyArray_|]
606−
535+array Value [] = [|Array V.empty|]
607− -- Single argument is directly converted.
536+array Encoding es = [|E.wrapArray|] `appE` foldr1 (<%>) es
608− [(e,eTy)] -> return (dispatchToEncoding jc conName tvMap eTy
537+array Value es = do
609− `appE` varE e)
538+ mv <- newName "mv"
610− -- Multiple arguments are converted to a JSON array.
539+ let newMV = bindS (varP mv)
611− es ->
540+ ([|VM.unsafeNew|] `appE`
612− return (array (foldr1 (<%>) [ dispatchToEncoding jc conName tvMap xTy
541+ litE (integerL $ fromIntegral (length es)))
613− `appE` varE x
542+ stmts = [ noBindS $
614− | (x,xTy) <- es
543+ [|VM.unsafeWrite|] `appE`
615− ]))
544+ varE mv `appE`
616− match (conP conName $ map varP args)
545+ litE (integerL ix) `appE`
617− (normalB $ sumToEncoding opts multiCons (null argTys') conName js)
546+ e
618− []
547+ | (ix, e) <- zip [(0::Integer)..] es
619−
548+ ]
620−-- Records.
549+ ret = noBindS $ [|return|] `appE` varE mv
621−argsToEncoding jc tvMap opts multiCons
550+ [|Array|] `appE`
622− info@ConstructorInfo { constructorName = conName
551+ (varE 'V.create `appE`
623− , constructorVariant = RecordConstructor fields
552+ doE (newMV:stmts++[ret]))
624− , constructorFields = argTys } =
625− case (unwrapUnaryRecords opts, not multiCons, argTys) of
626− (True,True,[_]) -> argsToEncoding jc tvMap opts multiCons
627− (info{constructorVariant = NormalConstructor})
628− _ -> do
629− argTys' <- mapM resolveTypeSynonyms argTys
630− args <- newNameList "arg" $ length argTys'
631− let exp = object objBody
632−
633− objBody = [|E.econcat|] `appE`
634− ([|intersperse E.comma|] `appE` pairs)
635− pairs | omitNothingFields opts = infixApp maybeFields
636− [|(++)|]
637− restFields
638− | otherwise = listE (map toPair argCons)
639−
640− argCons = zip3 args argTys' fields
641−
642− maybeFields = [|catMaybes|] `appE` listE (map maybeToPair maybes)
643−
644− restFields = listE (map toPair rest)
645−
646− (maybes, rest) = partition isMaybe argCons
647−
648− maybeToPair (arg, argTy, field) =
649− infixApp
650− (infixApp
651− (infixE
652− (Just $ toFieldName field <^> [|E.colon|])
653− [|(E.><)|]
654− Nothing)
655− [|(.)|]
656− (dispatchToEncoding jc conName tvMap argTy))
657− [|(<$>)|]
658− (varE arg)
659
553
660− toPair (arg, argTy, field) =
554+-- | Wrap an associative list of keys and quoted values in a quoted 'Object'.
661− toFieldName field
555+objectE :: [(String, ExpQ)] -> ExpQ
662− <:> dispatchToEncoding jc conName tvMap argTy
556+objectE = fromPairsE . mconcatE . fmap (uncurry pairE)
663− `appE` varE arg
664
557
665− toFieldName field = [|E.text|] `appE`
558+-- | 'mconcat' a list of fixed length.
666− ([|T.pack|] `appE` fieldLabelExp opts field)
559+--
560+-- > mconcatE [ [|x|], [|y|], [|z|] ] = [| x <> (y <> z) |]
561+mconcatE :: [ExpQ] -> ExpQ
562+mconcatE [] = [|Monoid.mempty|]
563+mconcatE [x] = x
564+mconcatE (x : xs) = infixApp x [|(Monoid.<>)|] (mconcatE xs)
667
565
668− match (conP conName $ map varP args)
566+fromPairsE :: ExpQ -> ExpQ
669− ( normalB
567+fromPairsE = ([|fromPairs|] `appE`)
670− $ if multiCons
671− then case sumEncoding opts of
672− TwoElemArray -> array $
673− encStr opts conName <%> exp
674− TaggedObject{tagFieldName} -> object $
675− ([|E.text (T.pack tagFieldName)|] <:>
676− encStr opts conName) <%>
677− objBody
678− ObjectWithSingleField -> object $
679− encStr opts conName <:> exp
680− UntaggedValue -> exp
681− else exp
682− ) []
683
568
684−-- Infix constructors.
569+-- | Create (an encoding of) a key-value pair.
685−argsToEncoding jc tvMap opts multiCons
570+--
686− ConstructorInfo { constructorName = conName
571+-- > pairE "k" [|v|] = [|pair "k" v|]
687− , constructorVariant = InfixConstructor
572+pairE :: String -> ExpQ -> ExpQ
688− , constructorFields = argTys } = do
573+pairE k v = [|pair k|] `appE` v
689− al <- newName "argL"
690− ar <- newName "argR"
691− [alTy,arTy] <- mapM resolveTypeSynonyms argTys
692− match (infixP (varP al) conName (varP ar))
693− ( normalB
694− $ sumToEncoding opts multiCons False conName
695− $ array (foldr1 (<%>) [ dispatchToEncoding jc conName tvMap aTy
696− `appE` varE a
697− | (a,aTy) <- [(al,alTy), (ar,arTy)]
698− ])
699− )
700− []
701
574
702--------------------------------------------------------------------------------
575--------------------------------------------------------------------------------
703-- FromJSON
576-- FromJSON
@@ -790,13 +663,13 @@ consFromJSON :: JSONClass
790consFromJSON _ _ _ _ [] = error $ "Data.Aeson.TH.consFromJSON: "
663consFromJSON _ _ _ _ [] = error $ "Data.Aeson.TH.consFromJSON: "
791 ++ "Not a single constructor given!"
664 ++ "Not a single constructor given!"
792
665
793−consFromJSON jc tName opts vars cons = do
666+consFromJSON jc tName opts instTys cons = do
794 value <- newName "value"
667 value <- newName "value"
795 pjs <- newNameList "_pj" $ arityInt jc
668 pjs <- newNameList "_pj" $ arityInt jc
796 pjls <- newNameList "_pjl" $ arityInt jc
669 pjls <- newNameList "_pjl" $ arityInt jc
797 let zippedPJs = zip pjs pjls
670 let zippedPJs = zip pjs pjls
798 interleavedPJs = interleave pjs pjls
671 interleavedPJs = interleave pjs pjls
799− lastTyVars = map varTToName $ drop (length vars - arityInt jc) vars
672+ lastTyVars = map varTToName $ drop (length instTys - arityInt jc) instTys
800 tvMap = M.fromList $ zip lastTyVars zippedPJs
673 tvMap = M.fromList $ zip lastTyVars zippedPJs
801 lamE (map varP $ interleavedPJs ++ [value]) $ lamExpr value tvMap
674 lamE (map varP $ interleavedPJs ++ [value]) $ lamExpr value tvMap
802
675
@@ -824,8 +697,7 @@ consFromJSON jc tName opts vars cons = do
824 [ liftM2 (,) (normalG $
697 [ liftM2 (,) (normalG $
825 infixApp (varE txt)
698 infixApp (varE txt)
826 [|(==)|]
699 [|(==)|]
827− ([|T.pack|] `appE`
828− conStringE opts conName)
700+ (conTxt opts conName)
829 )
701 )
830 ([|pure|] `appE` conE conName)
702 ([|pure|] `appE` conE conName)
831 | con <- cons
703 | con <- cons
@@ -917,7 +789,7 @@ consFromJSON jc tName opts vars cons = do
917 caseE (varE conVal)
789 caseE (varE conVal)
918 [ match (conP 'String [varP str])
790 [ match (conP 'String [varP str])
919 (guardedB
791 (guardedB
920− [ liftM2 (,) (normalG $ infixApp (varE str) [|(==)|] ([|T.pack|] `appE` conStringE opts conName)
792+ [ liftM2 (,) (normalG $ infixApp (varE str) [|(==)|] (conTxt opts conName)
921 )
793 )
922 ([|pure|] `appE` conE conName)
794 ([|pure|] `appE` conE conName)
923 ]
795 ]
@@ -1050,18 +922,38 @@ parseRecord :: JSONClass
1050 -> Name
922 -> Name
1051 -> [Name]
923 -> [Name]
1052 -> Name
924 -> Name
925+ -> Bool
1053 -> ExpQ
926 -> ExpQ
1054−parseRecord jc tvMap argTys opts tName conName fields obj =
927+parseRecord jc tvMap argTys opts tName conName fields obj inTaggedObject =
928+ (if rejectUnknownFields opts
929+ then infixApp checkUnknownRecords [|(>>)|]
930+ else id) $
1055 foldl' (\a b -> infixApp a [|(<*>)|] b)
931 foldl' (\a b -> infixApp a [|(<*>)|] b)
1056 (infixApp (conE conName) [|(<$>)|] x)
932 (infixApp (conE conName) [|(<$>)|] x)
1057 xs
933 xs
1058 where
934 where
935+ tagFieldNameAppender =
936+ if inTaggedObject then (tagFieldName (sumEncoding opts) :) else id
937+ knownFields = appE [|H.fromList|] $ listE $
938+ map (\knownName -> tupE [appE [|T.pack|] $ litE $ stringL knownName, [|()|]]) $
939+ tagFieldNameAppender $ map nameBase fields
940+ checkUnknownRecords =
941+ caseE (appE [|H.keys|] $ infixApp (varE obj) [|H.difference|] knownFields)
942+ [ match (listP []) (normalB [|return ()|]) []
943+ , newName "unknownFields" >>=
944+ \unknownFields -> match (varP unknownFields)
945+ (normalB $ appE [|fail|] $ infixApp
946+ (litE (stringL "Unknown fields: "))
947+ [|(++)|]
948+ (appE [|show|] (varE unknownFields)))
949+ []
950+ ]
1059 x:xs = [ [|lookupField|]
951 x:xs = [ [|lookupField|]
1060 `appE` dispatchParseJSON jc conName tvMap argTy
952 `appE` dispatchParseJSON jc conName tvMap argTy
1061 `appE` litE (stringL $ show tName)
953 `appE` litE (stringL $ show tName)
1062 `appE` litE (stringL $ constructorTagModifier opts $ nameBase conName)
954 `appE` litE (stringL $ constructorTagModifier opts $ nameBase conName)
1063 `appE` varE obj
955 `appE` varE obj
1064− `appE` ( [|T.pack|] `appE` fieldLabelExp opts field
956+ `appE` ( [|T.pack|] `appE` stringE (fieldLabel opts field)
1065 )
957 )
1066 | (field, argTy) <- zip fields argTys
958 | (field, argTy) <- zip fields argTys
1067 ]
959 ]
@@ -1130,7 +1022,7 @@ parseArgs jc tvMap tName opts
1130 , constructorFields = argTys }
1022 , constructorFields = argTys }
1131 (Left (_, obj)) = do
1023 (Left (_, obj)) = do
1132 argTys' <- mapM resolveTypeSynonyms argTys
1024 argTys' <- mapM resolveTypeSynonyms argTys
1133− parseRecord jc tvMap argTys' opts tName conName fields obj
1025+ parseRecord jc tvMap argTys' opts tName conName fields obj True
1134parseArgs jc tvMap tName opts
1026parseArgs jc tvMap tName opts
1135 info@ConstructorInfo { constructorName = conName
1027 info@ConstructorInfo { constructorName = conName
1136 , constructorVariant = RecordConstructor fields
1028 , constructorVariant = RecordConstructor fields
@@ -1145,7 +1037,7 @@ parseArgs jc tvMap tName opts
1145 argTys' <- mapM resolveTypeSynonyms argTys
1037 argTys' <- mapM resolveTypeSynonyms argTys
1146 caseE (varE valName)
1038 caseE (varE valName)
1147 [ match (conP 'Object [varP obj]) (normalB $
1039 [ match (conP 'Object [varP obj]) (normalB $
1148− parseRecord jc tvMap argTys' opts tName conName fields obj) []
1040+ parseRecord jc tvMap argTys' opts tName conName fields obj False) []
1149 , matchFailed tName conName "Object"
1041 , matchFailed tName conName "Object"
1150 ]
1042 ]
1151
1043
@@ -1234,6 +1126,11 @@ instance OVERLAPPABLE_ LookupField a where
1234instance INCOHERENT_ LookupField (Maybe a) where
1126instance INCOHERENT_ LookupField (Maybe a) where
1235 lookupField pj _ _ = parseOptionalFieldWith pj
1127 lookupField pj _ _ = parseOptionalFieldWith pj
1236
1128
1129+instance INCOHERENT_ LookupField (Semigroup.Option a) where
1130+ lookupField pj tName rec obj key =
1131+ fmap Semigroup.Option
1132+ (lookupField (fmap Semigroup.getOption . pj) tName rec obj key)
1133+
1237lookupFieldWith :: (Value -> Parser a) -> String -> String
1134lookupFieldWith :: (Value -> Parser a) -> String -> String
1238 -> Object -> T.Text -> Parser a
1135 -> Object -> T.Text -> Parser a
1239lookupFieldWith pj tName rec obj key =
1136lookupFieldWith pj tName rec obj key =
@@ -1241,9 +1138,6 @@ lookupFieldWith pj tName rec obj key =
1241 Nothing -> unknownFieldFail tName rec (T.unpack key)
1138 Nothing -> unknownFieldFail tName rec (T.unpack key)
1242 Just v -> pj v <?> Key key
1139 Just v -> pj v <?> Key key
1243
1140
1244−keyValuePairWith :: (v -> Value) -> T.Text -> v -> Pair
1245−keyValuePairWith tj name value = (name, tj value)
1246−
1247unknownFieldFail :: String -> String -> String -> Parser fail
1141unknownFieldFail :: String -> String -> String -> Parser fail
1248unknownFieldFail tName rec key =
1142unknownFieldFail tName rec key =
1249 fail $ printf "When parsing the record %s of type %s the key %s was not present."
1143 fail $ printf "When parsing the record %s of type %s the key %s was not present."
@@ -1326,23 +1220,27 @@ deriveJSONClass :: [(JSONFun, JSONClass -> Name -> Options -> [Type]
1326deriveJSONClass consFuns jc opts name = do
1220deriveJSONClass consFuns jc opts name = do
1327 info <- reifyDatatype name
1221 info <- reifyDatatype name
1328 case info of
1222 case info of
1329− DatatypeInfo { datatypeContext = ctxt
1223+ DatatypeInfo { datatypeContext = ctxt
1330− , datatypeName = parentName
1224+ , datatypeName = parentName
1225+#if MIN_VERSION_th_abstraction(0,3,0)
1226+ , datatypeInstTypes = instTys
1227+#else
1331− , datatypeVars = vars
1228+ , datatypeVars = instTys
1229+#endif
1332− , datatypeVariant = variant
1230+ , datatypeVariant = variant
1333− , datatypeCons = cons
1231+ , datatypeCons = cons
1334 } -> do
1232 } -> do
1335 (instanceCxt, instanceType)
1233 (instanceCxt, instanceType)
1336− <- buildTypeInstance parentName jc ctxt vars variant
1234+ <- buildTypeInstance parentName jc ctxt instTys variant
1337 (:[]) <$> instanceD (return instanceCxt)
1235 (:[]) <$> instanceD (return instanceCxt)
1338 (return instanceType)
1236 (return instanceType)
1339− (methodDecs parentName vars cons)
1237+ (methodDecs parentName instTys cons)
1340 where
1238 where
1341 methodDecs :: Name -> [Type] -> [ConstructorInfo] -> [Q Dec]
1239 methodDecs :: Name -> [Type] -> [ConstructorInfo] -> [Q Dec]
1342− methodDecs parentName vars cons = flip map consFuns $ \(jf, jfMaker) ->
1240+ methodDecs parentName instTys cons = flip map consFuns $ \(jf, jfMaker) ->
1343 funD (jsonFunValName jf (arity jc))
1241 funD (jsonFunValName jf (arity jc))
1344 [ clause []
1242 [ clause []
1345− (normalB $ jfMaker jc parentName opts vars cons)
1243+ (normalB $ jfMaker jc parentName opts instTys cons)
1346 []
1244 []
1347 ]
1245 ]
1348
1246
@@ -1358,17 +1256,21 @@ mkFunCommon :: (JSONClass -> Name -> Options -> [Type] -> [ConstructorInfo] -> Q
1358mkFunCommon consFun jc opts name = do
1256mkFunCommon consFun jc opts name = do
1359 info <- reifyDatatype name
1257 info <- reifyDatatype name
1360 case info of
1258 case info of
1361− DatatypeInfo { datatypeContext = ctxt
1259+ DatatypeInfo { datatypeContext = ctxt
1362− , datatypeName = parentName
1260+ , datatypeName = parentName
1261+#if MIN_VERSION_th_abstraction(0,3,0)
1262+ , datatypeInstTypes = instTys
1263+#else
1363− , datatypeVars = vars
1264+ , datatypeVars = instTys
1265+#endif
1364− , datatypeVariant = variant
1266+ , datatypeVariant = variant
1365− , datatypeCons = cons
1267+ , datatypeCons = cons
1366 } -> do
1268 } -> do
1367 -- We force buildTypeInstance here since it performs some checks for whether
1269 -- We force buildTypeInstance here since it performs some checks for whether
1368 -- or not the provided datatype's kind matches the derived method's
1270 -- or not the provided datatype's kind matches the derived method's
1369 -- typeclass, and produces errors if it can't.
1271 -- typeclass, and produces errors if it can't.
1370− !_ <- buildTypeInstance parentName jc ctxt vars variant
1272+ !_ <- buildTypeInstance parentName jc ctxt instTys variant
1371− consFun jc parentName opts vars cons
1273+ consFun jc parentName opts instTys cons
1372
1274
1373dispatchFunByType :: JSONClass
1275dispatchFunByType :: JSONClass
1374 -> JSONFun
1276 -> JSONFun
@@ -1413,10 +1315,13 @@ dispatchFunByType jc jf conName tvMap list ty = do
1413 (interleave rhsArgs rhsArgs)
1315 (interleave rhsArgs rhsArgs)
1414 else varE $ jsonFunValOrListName list jf Arity0
1316 else varE $ jsonFunValOrListName list jf Arity0
1415
1317
1318+dispatchToJSON
1319+ :: ToJSONFun -> JSONClass -> Name -> TyVarMap -> Type -> Q Exp
1320+dispatchToJSON target jc n tvMap =
1321+ dispatchFunByType jc (targetToJSONFun target) n tvMap False
1322+
1416−dispatchToJSON, dispatchToEncoding, dispatchParseJSON
1323+dispatchParseJSON
1417 :: JSONClass -> Name -> TyVarMap -> Type -> Q Exp
1324 :: JSONClass -> Name -> TyVarMap -> Type -> Q Exp
1418−dispatchToJSON jc n tvMap = dispatchFunByType jc ToJSON n tvMap False
1419−dispatchToEncoding jc n tvMap = dispatchFunByType jc ToEncoding n tvMap False
1420dispatchParseJSON jc n tvMap = dispatchFunByType jc ParseJSON n tvMap False
1325dispatchParseJSON jc n tvMap = dispatchFunByType jc ParseJSON n tvMap False
1421
1326
1422--------------------------------------------------------------------------------
1327--------------------------------------------------------------------------------
@@ -1698,21 +1603,13 @@ type TyVarMap = Map Name (Name, Name)
1698-- | Returns True if a Type has kind *.
1603-- | Returns True if a Type has kind *.
1699hasKindStar :: Type -> Bool
1604hasKindStar :: Type -> Bool
1700hasKindStar VarT{} = True
1605hasKindStar VarT{} = True
1701−#if MIN_VERSION_template_haskell(2,8,0)
1702hasKindStar (SigT _ StarT) = True
1606hasKindStar (SigT _ StarT) = True
1703−#else
1704−hasKindStar (SigT _ StarK) = True
1705−#endif
1706hasKindStar _ = False
1607hasKindStar _ = False
1707
1608
1708-- Returns True is a kind is equal to *, or if it is a kind variable.
1609-- Returns True is a kind is equal to *, or if it is a kind variable.
1709isStarOrVar :: Kind -> Bool
1610isStarOrVar :: Kind -> Bool
1710−#if MIN_VERSION_template_haskell(2,8,0)
1711isStarOrVar StarT = True
1611isStarOrVar StarT = True
1712isStarOrVar VarT{} = True
1612isStarOrVar VarT{} = True
1713−#else
1714−isStarOrVar StarK = True
1715−#endif
1716isStarOrVar _ = False
1613isStarOrVar _ = False
1717
1614
1718-- Generate a list of fresh names with a common prefix, and numbered suffixes.
1615-- Generate a list of fresh names with a common prefix, and numbered suffixes.
@@ -1770,9 +1667,7 @@ isTyFamily (ConT n) = do
1770#else
1667#else
1771 FamilyI (FamilyD TypeFam _ _ _) _ -> True
1668 FamilyI (FamilyD TypeFam _ _ _) _ -> True
1772#endif
1669#endif
1773−#if MIN_VERSION_template_haskell(2,9,0)
1774 FamilyI ClosedTypeFamilyD{} _ -> True
1670 FamilyI ClosedTypeFamilyD{} _ -> True
1775−#endif
1776 _ -> False
1671 _ -> False
1777isTyFamily _ = return False
1672isTyFamily _ = return False
1778
1673
@@ -1800,9 +1695,7 @@ mentionsName = go
1800 go :: Type -> [Name] -> Bool
1695 go :: Type -> [Name] -> Bool
1801 go (AppT t1 t2) names = go t1 names || go t2 names
1696 go (AppT t1 t2) names = go t1 names || go t2 names
1802 go (SigT t _k) names = go t names
1697 go (SigT t _k) names = go t names
1803−#if MIN_VERSION_template_haskell(2,8,0)
1804 || go _k names
1698 || go _k names
1805−#endif
1806 go (VarT n) names = n `elem` names
1699 go (VarT n) names = n `elem` names
1807 go _ _ = False
1700 go _ _ = False
1808
1701
@@ -1858,23 +1751,14 @@ uncurryTy t = ([], t :| [])
1858
1751
1859-- | Like uncurryType, except on a kind level.
1752-- | Like uncurryType, except on a kind level.
1860uncurryKind :: Kind -> NonEmpty Kind
1753uncurryKind :: Kind -> NonEmpty Kind
1861−#if MIN_VERSION_template_haskell(2,8,0)
1862uncurryKind = snd . uncurryTy
1754uncurryKind = snd . uncurryTy
1863−#else
1864−uncurryKind (ArrowK k1 k2) = k1 <| uncurryKind k2
1865−uncurryKind k = k :| []
1866−#endif
1867
1755
1868createKindChain :: Int -> Kind
1756createKindChain :: Int -> Kind
1869createKindChain = go starK
1757createKindChain = go starK
1870 where
1758 where
1871 go :: Kind -> Int -> Kind
1759 go :: Kind -> Int -> Kind
1872 go k 0 = k
1760 go k 0 = k
1873−#if MIN_VERSION_template_haskell(2,8,0)
1874 go k !n = go (AppT (AppT ArrowT StarT) k) (n - 1)
1761 go k !n = go (AppT (AppT ArrowT StarT) k) (n - 1)
1875−#else
1876− go k !n = go (ArrowK StarK k) (n - 1)
1877−#endif
1878
1762
1879-- | Makes a string literal expression from a constructor's name.
1763-- | Makes a string literal expression from a constructor's name.
1880conNameExp :: Options -> ConstructorInfo -> Q Exp
1764conNameExp :: Options -> ConstructorInfo -> Q Exp
@@ -1884,11 +1768,11 @@ conNameExp opts = litE
1884 . nameBase
1768 . nameBase
1885 . constructorName
1769 . constructorName
1886
1770
1887−-- | Creates a string literal expression from a record field label.
1771+-- | Extracts a record field label.
1888−fieldLabelExp :: Options -- ^ Encoding options
1772+fieldLabel :: Options -- ^ Encoding options
1889− -> Name
1773+ -> Name
1890− -> Q Exp
1774+ -> String
1891−fieldLabelExp opts = litE . stringL . fieldLabelModifier opts . nameBase
1775+fieldLabel opts = fieldLabelModifier opts . nameBase
1892
1776
1893-- | The name of the outermost 'Value' constructor.
1777-- | The name of the outermost 'Value' constructor.
1894valueConName :: Value -> String
1778valueConName :: Value -> String
@@ -1928,11 +1812,7 @@ canEtaReduce remaining dropped =
1928-------------------------------------------------------------------------------
1812-------------------------------------------------------------------------------
1929
1813
1930applySubstitutionKind :: Map Name Kind -> Type -> Type
1814applySubstitutionKind :: Map Name Kind -> Type -> Type
1931−#if MIN_VERSION_template_haskell(2,8,0)
1932applySubstitutionKind = applySubstitution
1815applySubstitutionKind = applySubstitution
1933−#else
1934−applySubstitutionKind _ t = t
1935−#endif
1936
1816
1937substNameWithKind :: Name -> Kind -> Type -> Type
1817substNameWithKind :: Name -> Kind -> Type -> Type
1938substNameWithKind n k = applySubstitutionKind (M.singleton n k)
1818substNameWithKind n k = applySubstitutionKind (M.singleton n k)
@@ -2021,6 +1901,13 @@ data Direction = To | From
2021-- | A representation of which typeclass method is being spliced in.
1901-- | A representation of which typeclass method is being spliced in.
2022data JSONFun = ToJSON | ToEncoding | ParseJSON
1902data JSONFun = ToJSON | ToEncoding | ParseJSON
2023
1903
1904+-- | A refinement of JSONFun to [ToJSON, ToEncoding].
1905+data ToJSONFun = Value | Encoding
1906+
1907+targetToJSONFun :: ToJSONFun -> JSONFun
1908+targetToJSONFun Value = ToJSON
1909+targetToJSONFun Encoding = ToEncoding
1910+
2024-- | A representation of which typeclass is being derived.
1911-- | A representation of which typeclass is being derived.
2025data JSONClass = JSONClass { direction :: Direction, arity :: Arity }
1912data JSONClass = JSONClass { direction :: Direction, arity :: Arity }
2026
1913
@@ -2089,9 +1976,7 @@ data StarKindStatus = NotKindStar
2089canRealizeKindStar :: Type -> StarKindStatus
1976canRealizeKindStar :: Type -> StarKindStatus
2090canRealizeKindStar t = case t of
1977canRealizeKindStar t = case t of
2091 _ | hasKindStar t -> KindStar
1978 _ | hasKindStar t -> KindStar
2092−#if MIN_VERSION_template_haskell(2,8,0)
2093 SigT _ (VarT k) -> IsKindVar k
1979 SigT _ (VarT k) -> IsKindVar k
2094−#endif
2095 _ -> NotKindStar
1980 _ -> NotKindStar
2096
1981
2097-- | Returns 'Just' the kind variable 'Name' of a 'StarKindStatus' if it exists.
1982-- | Returns 'Just' the kind variable 'Name' of a 'StarKindStatus' if it exists.
Data/Aeson/Text.hs+5−4
@@ -1,4 +1,5 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
3-- |
4-- |
4-- Module: Data.Aeson.Text
5-- Module: Data.Aeson.Text
@@ -14,18 +15,18 @@
14--
15--
15-- You can use the conversions to 'Builder's when embedding JSON messages as
16-- You can use the conversions to 'Builder's when embedding JSON messages as
16-- parts of a protocol.
17-- parts of a protocol.
18+
17module Data.Aeson.Text
19module Data.Aeson.Text
18 (
20 (
19 encodeToLazyText
21 encodeToLazyText
20 , encodeToTextBuilder
22 , encodeToTextBuilder
21 ) where
23 ) where
22
24
23−import Prelude ()
24import Prelude.Compat
25import Prelude.Compat
25
26
26import Data.Aeson.Types (Value(..), ToJSON(..))
27import Data.Aeson.Types (Value(..), ToJSON(..))
27import Data.Aeson.Encoding (encodingToLazyByteString)
28import Data.Aeson.Encoding (encodingToLazyByteString)
28−import Data.Monoid ((<>))
29+import Data.Semigroup ((<>))
29import Data.Scientific (FPFormat(..), Scientific, base10Exponent)
30import Data.Scientific (FPFormat(..), Scientific, base10Exponent)
30import Data.Text.Lazy.Builder
31import Data.Text.Lazy.Builder
31import Data.Text.Lazy.Builder.Scientific (formatScientificBuilder)
32import Data.Text.Lazy.Builder.Scientific (formatScientificBuilder)
@@ -46,8 +47,8 @@ encodeToLazyText = LT.decodeUtf8 . encodingToLazyByteString . toEncoding
46-- embedded efficiently in a text-based protocol.
47-- embedded efficiently in a text-based protocol.
47--
48--
48-- If you are going to immediately encode straight to a
49-- If you are going to immediately encode straight to a
49−-- 'L.ByteString', it is more efficient to use 'encodeToBuilder'
50+-- 'L.ByteString', it is more efficient to use 'encode' (lazy ByteString)
50−-- instead.
51+-- or @'fromEncoding' . 'toEncoding'@ (ByteString.Builder) instead.
51--
52--
52-- /Note:/ Uses 'toJSON'
53-- /Note:/ Uses 'toJSON'
53encodeToTextBuilder :: ToJSON a => a -> Builder
54encodeToTextBuilder :: ToJSON a => a -> Builder
Data/Aeson/Types.hs+23−2
@@ -1,3 +1,4 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1-- |
2-- |
2-- Module: Data.Aeson.Types
3-- Module: Data.Aeson.Types
3-- Copyright: (c) 2011-2016 Bryan O'Sullivan
4-- Copyright: (c) 2011-2016 Bryan O'Sullivan
@@ -25,6 +26,7 @@ module Data.Aeson.Types
25 -- * Convenience types and functions
26 -- * Convenience types and functions
26 , DotNetTime(..)
27 , DotNetTime(..)
27 , typeMismatch
28 , typeMismatch
29+ , unexpected
28 -- * Type conversion
30 -- * Type conversion
29 , Parser
31 , Parser
30 , Result(..)
32 , Result(..)
@@ -36,6 +38,7 @@ module Data.Aeson.Types
36 , ToJSON(..)
38 , ToJSON(..)
37 , KeyValue(..)
39 , KeyValue(..)
38 , modifyFailure
40 , modifyFailure
41+ , prependFailure
39 , parserThrowError
42 , parserThrowError
40 , parserCatchError
43 , parserCatchError
41
44
@@ -50,6 +53,12 @@ module Data.Aeson.Types
50 , coerceFromJSONKeyFunction
53 , coerceFromJSONKeyFunction
51 , mapFromJSONKeyFunction
54 , mapFromJSONKeyFunction
52
55
56+ -- *** Generic keys
57+ , GToJSONKey()
58+ , genericToJSONKey
59+ , GFromJSONKey()
60+ , genericFromJSONKey
61+
53 -- ** Liftings to unary and binary type constructors
62 -- ** Liftings to unary and binary type constructors
54 , FromJSON1(..)
63 , FromJSON1(..)
55 , parseJSON1
64 , parseJSON1
@@ -81,9 +90,9 @@ module Data.Aeson.Types
81 , withObject
90 , withObject
82 , withText
91 , withText
83 , withArray
92 , withArray
84− , withNumber
85 , withScientific
93 , withScientific
86 , withBool
94 , withBool
95+ , withEmbeddedJSON
87
96
88 , pairs
97 , pairs
89 , foldable
98 , foldable
@@ -115,6 +124,7 @@ module Data.Aeson.Types
115 , sumEncoding
124 , sumEncoding
116 , unwrapUnaryRecords
125 , unwrapUnaryRecords
117 , tagSingleConstructors
126 , tagSingleConstructors
127+ , rejectUnknownFields
118
128
119 -- ** Options utilities
129 -- ** Options utilities
120 , SumEncoding(..)
130 , SumEncoding(..)
@@ -122,9 +132,20 @@ module Data.Aeson.Types
122 , camelTo2
132 , camelTo2
123 , defaultOptions
133 , defaultOptions
124 , defaultTaggedObject
134 , defaultTaggedObject
135+
136+ -- ** Options for object keys
137+ , JSONKeyOptions
138+ , keyModifier
139+ , defaultJSONKeyOptions
140+
141+ -- * Parsing context
142+ , (<?>)
143+ , JSONPath
144+ , JSONPathElement(..)
145+ , formatPath
146+ , formatRelativePath
125 ) where
147 ) where
126
148
127−import Prelude ()
128import Prelude.Compat
149import Prelude.Compat
129
150
130import Data.Aeson.Encoding (Encoding, unsafeToEncoding, fromEncoding, Series, pairs)
151import Data.Aeson.Encoding (Encoding, unsafeToEncoding, fromEncoding, Series, pairs)
Data/Aeson/Types/Class.hs+8−3
@@ -4,8 +4,8 @@
4{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GADTs #-}
5{-# LANGUAGE GADTs #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
7+{-# LANGUAGE NoImplicitPrelude #-}
7{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE ScopedTypeVariables #-}
8−{-# LANGUAGE TypeSynonymInstances #-}
9
9
10-- |
10-- |
11-- Module: Data.Aeson.Types.Class
11-- Module: Data.Aeson.Types.Class
@@ -58,6 +58,11 @@ module Data.Aeson.Types.Class
58 , fromJSONKeyCoerce
58 , fromJSONKeyCoerce
59 , coerceFromJSONKeyFunction
59 , coerceFromJSONKeyFunction
60 , mapFromJSONKeyFunction
60 , mapFromJSONKeyFunction
61+ -- ** Generic keys
62+ , GToJSONKey()
63+ , genericToJSONKey
64+ , GFromJSONKey()
65+ , genericFromJSONKey
61 -- * Object key-value pairs
66 -- * Object key-value pairs
62 , KeyValue(..)
67 , KeyValue(..)
63
68
@@ -70,14 +75,15 @@ module Data.Aeson.Types.Class
70 , withObject
75 , withObject
71 , withText
76 , withText
72 , withArray
77 , withArray
73− , withNumber
74 , withScientific
78 , withScientific
75 , withBool
79 , withBool
80+ , withEmbeddedJSON
76
81
77 -- * Functions
82 -- * Functions
78 , fromJSON
83 , fromJSON
79 , ifromJSON
84 , ifromJSON
80 , typeMismatch
85 , typeMismatch
86+ , unexpected
81 , parseField
87 , parseField
82 , parseFieldMaybe
88 , parseFieldMaybe
83 , parseFieldMaybe'
89 , parseFieldMaybe'
@@ -91,7 +97,6 @@ module Data.Aeson.Types.Class
91 , (.!=)
97 , (.!=)
92 ) where
98 ) where
93
99
94−import Prelude ()
95
100
96import Data.Aeson.Types.FromJSON
101import Data.Aeson.Types.FromJSON
97import Data.Aeson.Types.Generic (One, Zero)
102import Data.Aeson.Types.Generic (One, Zero)
Data/Aeson/Types/FromJSON.hs+821−419
@@ -5,19 +5,16 @@
5{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GADTs #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8−{-# LANGUAGE NamedFieldPuns #-}
8+{-# LANGUAGE NoImplicitPrelude #-}
9{-# LANGUAGE OverloadedStrings #-}
9{-# LANGUAGE OverloadedStrings #-}
10+{-# LANGUAGE PolyKinds #-}
10{-# LANGUAGE RecordWildCards #-}
11{-# LANGUAGE RecordWildCards #-}
11{-# LANGUAGE ScopedTypeVariables #-}
12{-# LANGUAGE ScopedTypeVariables #-}
12{-# LANGUAGE TypeOperators #-}
13{-# LANGUAGE TypeOperators #-}
13−{-# LANGUAGE TypeSynonymInstances #-}
14+{-# LANGUAGE TupleSections #-}
14{-# LANGUAGE UndecidableInstances #-}
15{-# LANGUAGE UndecidableInstances #-}
15{-# LANGUAGE ViewPatterns #-}
16{-# LANGUAGE ViewPatterns #-}
16
17
17−#if __GLASGOW_HASKELL__ >= 706
18−{-# LANGUAGE PolyKinds #-}
19−#endif
20−
21#include "incoherent-compat.h"
18#include "incoherent-compat.h"
22#include "overlapping-compat.h"
19#include "overlapping-compat.h"
23
20
@@ -45,6 +42,9 @@ module Data.Aeson.Types.FromJSON
45 , coerceFromJSONKeyFunction
42 , coerceFromJSONKeyFunction
46 , mapFromJSONKeyFunction
43 , mapFromJSONKeyFunction
47
44
45+ , GFromJSONKey()
46+ , genericFromJSONKey
47+
48 -- * List functions
48 -- * List functions
49 , listParser
49 , listParser
50
50
@@ -52,20 +52,22 @@ module Data.Aeson.Types.FromJSON
52 , withObject
52 , withObject
53 , withText
53 , withText
54 , withArray
54 , withArray
55− , withNumber
56 , withScientific
55 , withScientific
57 , withBool
56 , withBool
57+ , withEmbeddedJSON
58
58
59 -- * Functions
59 -- * Functions
60 , fromJSON
60 , fromJSON
61 , ifromJSON
61 , ifromJSON
62 , typeMismatch
62 , typeMismatch
63+ , unexpected
63 , parseField
64 , parseField
64 , parseFieldMaybe
65 , parseFieldMaybe
65 , parseFieldMaybe'
66 , parseFieldMaybe'
66 , explicitParseField
67 , explicitParseField
67 , explicitParseFieldMaybe
68 , explicitParseFieldMaybe
68 , explicitParseFieldMaybe'
69 , explicitParseFieldMaybe'
70+ , parseIndexedJSON
69 -- ** Operators
71 -- ** Operators
70 , (.:)
72 , (.:)
71 , (.:?)
73 , (.:?)
@@ -76,17 +78,16 @@ module Data.Aeson.Types.FromJSON
76 , parseOptionalFieldWith
78 , parseOptionalFieldWith
77 ) where
79 ) where
78
80
79−import Prelude ()
80import Prelude.Compat
81import Prelude.Compat
81
82
82−import Control.Applicative ((<|>), Const(..))
83+import Control.Applicative ((<|>), Const(..), liftA2)
83−import Control.Monad ((<=<), zipWithM)
84+import Control.Monad (zipWithM)
84import Data.Aeson.Internal.Functions (mapKey)
85import Data.Aeson.Internal.Functions (mapKey)
86+import Data.Aeson.Parser.Internal (eitherDecodeWith, jsonEOF)
85import Data.Aeson.Types.Generic
87import Data.Aeson.Types.Generic
86import Data.Aeson.Types.Internal
88import Data.Aeson.Types.Internal
87−import Data.Attoparsec.Number (Number(..))
88import Data.Bits (unsafeShiftR)
89import Data.Bits (unsafeShiftR)
89−import Data.Fixed (Fixed, HasResolution)
90+import Data.Fixed (Fixed, HasResolution (resolution), Nano)
90import Data.Functor.Compose (Compose(..))
91import Data.Functor.Compose (Compose(..))
91import Data.Functor.Identity (Identity(..))
92import Data.Functor.Identity (Identity(..))
92import Data.Functor.Product (Product(..))
93import Data.Functor.Product (Product(..))
@@ -95,18 +96,21 @@ import Data.Hashable (Hashable(..))
95import Data.Int (Int16, Int32, Int64, Int8)
96import Data.Int (Int16, Int32, Int64, Int8)
96import Data.List.NonEmpty (NonEmpty(..))
97import Data.List.NonEmpty (NonEmpty(..))
97import Data.Maybe (fromMaybe)
98import Data.Maybe (fromMaybe)
98−import Data.Monoid ((<>))
99+import Data.Semigroup ((<>))
99import Data.Proxy (Proxy(..))
100import Data.Proxy (Proxy(..))
100import Data.Ratio ((%), Ratio)
101import Data.Ratio ((%), Ratio)
101−import Data.Scientific (Scientific)
102+import Data.Scientific (Scientific, base10Exponent)
102import Data.Tagged (Tagged(..))
103import Data.Tagged (Tagged(..))
103import Data.Text (Text, pack, unpack)
104import Data.Text (Text, pack, unpack)
104import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)
105import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)
105−import Data.Time.Format (parseTime)
106+import Data.Time.Calendar.Compat (CalendarDiffDays (..), DayOfWeek (..))
107+import Data.Time.LocalTime.Compat (CalendarDiffTime (..))
108+import Data.Time.Clock.System.Compat (SystemTime (..))
106−import Data.Time.Locale.Compat (defaultTimeLocale)
109+import Data.Time.Format.Compat (parseTimeM, defaultTimeLocale)
107import Data.Traversable as Tr (sequence)
110import Data.Traversable as Tr (sequence)
108import Data.Vector (Vector)
111import Data.Vector (Vector)
109import Data.Version (Version, parseVersion)
112import Data.Version (Version, parseVersion)
113+import Data.Void (Void)
110import Data.Word (Word16, Word32, Word64, Word8)
114import Data.Word (Word16, Word32, Word64, Word8)
111import Foreign.Storable (Storable)
115import Foreign.Storable (Storable)
112import Foreign.C.Types (CTime (..))
116import Foreign.C.Types (CTime (..))
@@ -116,6 +120,7 @@ import Text.ParserCombinators.ReadP (readP_to_S)
116import Unsafe.Coerce (unsafeCoerce)
120import Unsafe.Coerce (unsafeCoerce)
117import qualified Data.Aeson.Parser.Time as Time
121import qualified Data.Aeson.Parser.Time as Time
118import qualified Data.Attoparsec.ByteString.Char8 as A (endOfInput, parseOnly, scientific)
122import qualified Data.Attoparsec.ByteString.Char8 as A (endOfInput, parseOnly, scientific)
123+import qualified Data.ByteString.Lazy as L
119import qualified Data.DList as DList
124import qualified Data.DList as DList
120import qualified Data.HashMap.Strict as H
125import qualified Data.HashMap.Strict as H
121import qualified Data.HashSet as HashSet
126import qualified Data.HashSet as HashSet
@@ -138,18 +143,20 @@ import qualified Data.Vector.Primitive as VP
138import qualified Data.Vector.Storable as VS
143import qualified Data.Vector.Storable as VS
139import qualified Data.Vector.Unboxed as VU
144import qualified Data.Vector.Unboxed as VU
140
145
141−#ifndef HAS_COERCIBLE
146+import qualified GHC.Exts as Exts
142−#define HAS_COERCIBLE (__GLASGOW_HASKELL__ >= 707)
147+import qualified Data.Primitive.Array as PM
148+import qualified Data.Primitive.SmallArray as PM
149+import qualified Data.Primitive.Types as PM
150+
151+#if MIN_VERSION_primitive(0,6,4)
152+#if !MIN_VERSION_primitive(0,7,0)
153+import qualified Data.Primitive.UnliftedArray as PM
143#endif
154#endif
155+import qualified Data.Primitive.PrimArray as PM
156+#endif
157+
144
158
145−#if HAS_COERCIBLE
146import Data.Coerce (Coercible, coerce)
159import Data.Coerce (Coercible, coerce)
147−coerce' :: Coercible a b => a -> b
148−coerce' = coerce
149−#else
150−coerce' :: a -> b
151−coerce' = unsafeCoerce
152−#endif
153
160
154parseIndexedJSON :: (Value -> Parser a) -> Int -> Value -> Parser a
161parseIndexedJSON :: (Value -> Parser a) -> Int -> Value -> Parser a
155parseIndexedJSON p idx value = p value <?> Index idx
162parseIndexedJSON p idx value = p value <?> Index idx
@@ -158,7 +165,7 @@ parseIndexedJSON p idx value = p value <?> Index idx
158parseIndexedJSONPair :: (Value -> Parser a) -> (Value -> Parser b) -> Int -> Value -> Parser (a, b)
165parseIndexedJSONPair :: (Value -> Parser a) -> (Value -> Parser b) -> Int -> Value -> Parser (a, b)
159parseIndexedJSONPair keyParser valParser idx value = p value <?> Index idx
166parseIndexedJSONPair keyParser valParser idx value = p value <?> Index idx
160 where
167 where
161− p = withArray "(k,v)" $ \ab ->
168+ p = withArray "(k, v)" $ \ab ->
162 let n = V.length ab
169 let n = V.length ab
163 in if n == 2
170 in if n == 2
164 then (,) <$> parseJSONElemAtIndex keyParser 0 ab
171 then (,) <$> parseJSONElemAtIndex keyParser 0 ab
@@ -170,43 +177,34 @@ parseIndexedJSONPair keyParser valParser idx value = p value <?> Index idx
170parseJSONElemAtIndex :: (Value -> Parser a) -> Int -> V.Vector Value -> Parser a
177parseJSONElemAtIndex :: (Value -> Parser a) -> Int -> V.Vector Value -> Parser a
171parseJSONElemAtIndex p idx ary = p (V.unsafeIndex ary idx) <?> Index idx
178parseJSONElemAtIndex p idx ary = p (V.unsafeIndex ary idx) <?> Index idx
172
179
173−scientificToNumber :: Scientific -> Number
174−scientificToNumber s
175− | e < 0 = D $ Scientific.toRealFloat s
176− | otherwise = I $ c * 10 ^ e
177− where
178− e = Scientific.base10Exponent s
179− c = Scientific.coefficient s
180−{-# INLINE scientificToNumber #-}
181−
182parseRealFloat :: RealFloat a => String -> Value -> Parser a
180parseRealFloat :: RealFloat a => String -> Value -> Parser a
183−parseRealFloat _ (Number s) = pure $ Scientific.toRealFloat s
181+parseRealFloat _ (Number s) = pure $ Scientific.toRealFloat s
184−parseRealFloat _ Null = pure (0/0)
182+parseRealFloat _ Null = pure (0/0)
185−parseRealFloat expected v = typeMismatch expected v
183+parseRealFloat name v = prependContext name (unexpected v)
186{-# INLINE parseRealFloat #-}
184{-# INLINE parseRealFloat #-}
187
185
188−parseIntegralFromScientific :: forall a. Integral a => String -> Scientific -> Parser a
186+parseIntegralFromScientific :: forall a. Integral a => Scientific -> Parser a
189−parseIntegralFromScientific expected s =
187+parseIntegralFromScientific s =
190 case Scientific.floatingOrInteger s :: Either Double a of
188 case Scientific.floatingOrInteger s :: Either Double a of
191 Right x -> pure x
189 Right x -> pure x
192− Left _ -> fail $ "expected " ++ expected ++ ", encountered floating number " ++ show s
190+ Left _ -> fail $ "unexpected floating number " ++ show s
193{-# INLINE parseIntegralFromScientific #-}
191{-# INLINE parseIntegralFromScientific #-}
194
192
195parseIntegral :: Integral a => String -> Value -> Parser a
193parseIntegral :: Integral a => String -> Value -> Parser a
196−parseIntegral expected =
194+parseIntegral name =
197− withScientific expected $ parseIntegralFromScientific expected
195+ prependContext name . withBoundedScientific' parseIntegralFromScientific
198{-# INLINE parseIntegral #-}
196{-# INLINE parseIntegral #-}
199
197
200−parseBoundedIntegralFromScientific :: (Bounded a, Integral a) => String -> Scientific -> Parser a
198+parseBoundedIntegralFromScientific :: (Bounded a, Integral a) => Scientific -> Parser a
201−parseBoundedIntegralFromScientific expected s = maybe
199+parseBoundedIntegralFromScientific s = maybe
202− (fail $ expected ++ " is either floating or will cause over or underflow: " ++ show s)
200+ (fail $ "value is either floating or will cause over or underflow " ++ show s)
203 pure
201 pure
204 (Scientific.toBoundedInteger s)
202 (Scientific.toBoundedInteger s)
205{-# INLINE parseBoundedIntegralFromScientific #-}
203{-# INLINE parseBoundedIntegralFromScientific #-}
206
204
207parseBoundedIntegral :: (Bounded a, Integral a) => String -> Value -> Parser a
205parseBoundedIntegral :: (Bounded a, Integral a) => String -> Value -> Parser a
208−parseBoundedIntegral expected =
206+parseBoundedIntegral name =
209− withScientific expected $ parseBoundedIntegralFromScientific expected
207+ prependContext name . withScientific' parseBoundedIntegralFromScientific
210{-# INLINE parseBoundedIntegral #-}
208{-# INLINE parseBoundedIntegral #-}
211
209
212parseScientificText :: Text -> Parser Scientific
210parseScientificText :: Text -> Parser Scientific
@@ -216,13 +214,20 @@ parseScientificText
216 . T.encodeUtf8
214 . T.encodeUtf8
217
215
218parseIntegralText :: Integral a => String -> Text -> Parser a
216parseIntegralText :: Integral a => String -> Text -> Parser a
219−parseIntegralText expected t =
217+parseIntegralText name t =
218+ prependContext name $
219+ parseScientificText t
220+ >>= rejectLargeExponent
220− parseScientificText t >>= parseIntegralFromScientific expected
221+ >>= parseIntegralFromScientific
222+ where
223+ rejectLargeExponent :: Scientific -> Parser Scientific
224+ rejectLargeExponent s = withBoundedScientific' pure (Number s)
221{-# INLINE parseIntegralText #-}
225{-# INLINE parseIntegralText #-}
222
226
223parseBoundedIntegralText :: (Bounded a, Integral a) => String -> Text -> Parser a
227parseBoundedIntegralText :: (Bounded a, Integral a) => String -> Text -> Parser a
224−parseBoundedIntegralText expected t =
228+parseBoundedIntegralText name t =
229+ prependContext name $
225− parseScientificText t >>= parseBoundedIntegralFromScientific expected
230+ parseScientificText t >>= parseBoundedIntegralFromScientific
226
231
227parseOptionalFieldWith :: (Value -> Parser (Maybe a))
232parseOptionalFieldWith :: (Value -> Parser (Maybe a))
228 -> Object -> Text -> Parser (Maybe a)
233 -> Object -> Text -> Parser (Maybe a)
@@ -282,14 +287,21 @@ genericLiftParseJSON opts pj pjl = fmap to1 . gParseJSON opts (From1Args pj pjl)
282--
287--
283-- The basic ways to signal a failed conversion are as follows:
288-- The basic ways to signal a failed conversion are as follows:
284--
289--
290+-- * 'fail' yields a custom error message: it is the recommended way of
291+-- reporting a failure;
292+--
285−-- * 'empty' and 'mzero' work, but are terse and uninformative;
293+-- * 'Control.Applicative.empty' (or 'Control.Monad.mzero') is uninformative:
294+-- use it when the error is meant to be caught by some @('<|>')@;
286--
295--
287−-- * 'fail' yields a custom error message;
296+-- * 'typeMismatch' can be used to report a failure when the encountered value
297+-- is not of the expected JSON type; 'unexpected' is an appropriate alternative
298+-- when more than one type may be expected, or to keep the expected type
299+-- implicit.
288--
300--
289−-- * 'typeMismatch' produces an informative message for cases when the
301+-- 'prependFailure' (or 'modifyFailure') add more information to a parser's
290−-- value encountered is not of the expected type.
302+-- error messages.
291--
303--
292−-- An example type and instance using 'typeMismatch':
304+-- An example type and instance using 'typeMismatch' and 'prependFailure':
293--
305--
294-- @
306-- @
295-- \-- Allow ourselves to write 'Text' literals.
307-- \-- Allow ourselves to write 'Text' literals.
@@ -303,20 +315,22 @@ genericLiftParseJSON opts pj pjl = fmap to1 . gParseJSON opts (From1Args pj pjl)
303-- '<*>' v '.:' \"y\"
315-- '<*>' v '.:' \"y\"
304--
316--
305-- \-- We do not expect a non-'Object' value here.
317-- \-- We do not expect a non-'Object' value here.
306−-- \-- We could use 'mzero' to fail, but 'typeMismatch'
318+-- \-- We could use 'Control.Applicative.empty' to fail, but 'typeMismatch'
307-- \-- gives a much more informative error message.
319-- \-- gives a much more informative error message.
320+-- 'parseJSON' invalid =
321+-- 'prependFailure' "parsing Coord failed, "
308−-- 'parseJSON' invalid = 'typeMismatch' \"Coord\" invalid
322+-- ('typeMismatch' \"Object\" invalid)
309-- @
323-- @
310--
324--
311-- For this common case of only being concerned with a single
325-- For this common case of only being concerned with a single
312−-- type of JSON value, the functions 'withObject', 'withNumber', etc.
326+-- type of JSON value, the functions 'withObject', 'withScientific', etc.
313-- are provided. Their use is to be preferred when possible, since
327-- are provided. Their use is to be preferred when possible, since
314-- they are more terse. Using 'withObject', we can rewrite the above instance
328-- they are more terse. Using 'withObject', we can rewrite the above instance
315-- (assuming the same language extension and data type) as:
329-- (assuming the same language extension and data type) as:
316--
330--
317-- @
331-- @
318-- instance 'FromJSON' Coord where
332-- instance 'FromJSON' Coord where
319−-- 'parseJSON' = 'withObject' \"Coord\" $ \v -> Coord
333+-- 'parseJSON' = 'withObject' \"Coord\" $ \\v -> Coord
320-- '<$>' v '.:' \"x\"
334-- '<$>' v '.:' \"x\"
321-- '<*>' v '.:' \"y\"
335-- '<*>' v '.:' \"y\"
322-- @
336-- @
@@ -348,7 +362,7 @@ genericLiftParseJSON opts pj pjl = fmap to1 . gParseJSON opts (From1Args pj pjl)
348-- @
362-- @
349--
363--
350-- The default implementation will be equivalent to
364-- The default implementation will be equivalent to
351−-- @parseJSON = 'genericParseJSON' 'defaultOptions'@; If you need different
365+-- @parseJSON = 'genericParseJSON' 'defaultOptions'@; if you need different
352-- options, you can customize the generic decoding by defining:
366-- options, you can customize the generic decoding by defining:
353--
367--
354-- @
368-- @
@@ -366,13 +380,11 @@ class FromJSON a where
366 parseJSON = genericParseJSON defaultOptions
380 parseJSON = genericParseJSON defaultOptions
367
381
368 parseJSONList :: Value -> Parser [a]
382 parseJSONList :: Value -> Parser [a]
369− parseJSONList (Array a)
383+ parseJSONList = withArray "[]" $ \a ->
370− = zipWithM (parseIndexedJSON parseJSON) [0..]
384+ zipWithM (parseIndexedJSON parseJSON) [0..]
371 . V.toList
385 . V.toList
372 $ a
386 $ a
373
387
374− parseJSONList v = typeMismatch "[a]" v
375−
376-------------------------------------------------------------------------------
388-------------------------------------------------------------------------------
377-- Classes and types for map keys
389-- Classes and types for map keys
378-------------------------------------------------------------------------------
390-------------------------------------------------------------------------------
@@ -384,6 +396,16 @@ class FromJSON a where
384-- > newtype SomeId = SomeId { getSomeId :: Text }
396-- > newtype SomeId = SomeId { getSomeId :: Text }
385-- > deriving (Eq,Ord,Hashable,FromJSONKey)
397-- > deriving (Eq,Ord,Hashable,FromJSONKey)
386--
398--
399+-- If you have a sum of nullary constructors, you may use the generic
400+-- implementation:
401+--
402+-- @
403+-- data Color = Red | Green | Blue
404+-- deriving Generic
405+--
406+-- instance 'FromJSONKey' Color where
407+-- 'fromJSONKey' = 'genericFromJSONKey' 'defaultJSONKeyOptions'
408+-- @
387class FromJSONKey a where
409class FromJSONKey a where
388 -- | Strategy for parsing the key of a map-like container.
410 -- | Strategy for parsing the key of a map-like container.
389 fromJSONKey :: FromJSONKeyFunction a
411 fromJSONKey :: FromJSONKeyFunction a
@@ -406,11 +428,7 @@ class FromJSONKey a where
406-- At the moment this type is intentionally not exported. 'FromJSONKeyFunction'
428-- At the moment this type is intentionally not exported. 'FromJSONKeyFunction'
407-- can be inspected, but cannot be constructed.
429-- can be inspected, but cannot be constructed.
408data CoerceText a where
430data CoerceText a where
409−#if HAS_COERCIBLE
410 CoerceText :: Coercible Text a => CoerceText a
431 CoerceText :: Coercible Text a => CoerceText a
411−#else
412− CoerceText :: CoerceText a
413−#endif
414
432
415-- | This type is related to 'ToJSONKeyFunction'. If 'FromJSONKeyValue' is used in the
433-- | This type is related to 'ToJSONKeyFunction'. If 'FromJSONKeyValue' is used in the
416-- 'FromJSONKey' instance, then 'ToJSONKeyValue' should be used in the 'ToJSONKey'
434-- 'FromJSONKey' instance, then 'ToJSONKeyValue' should be used in the 'ToJSONKey'
@@ -430,7 +448,7 @@ data FromJSONKeyFunction a
430
448
431-- | Only law abiding up to interpretation
449-- | Only law abiding up to interpretation
432instance Functor FromJSONKeyFunction where
450instance Functor FromJSONKeyFunction where
433− fmap h (FromJSONKeyCoerce CoerceText) = FromJSONKeyText (h . coerce')
451+ fmap h (FromJSONKeyCoerce CoerceText) = FromJSONKeyText (h . coerce)
434 fmap h (FromJSONKeyText f) = FromJSONKeyText (h . f)
452 fmap h (FromJSONKeyText f) = FromJSONKeyText (h . f)
435 fmap h (FromJSONKeyTextParser f) = FromJSONKeyTextParser (fmap h . f)
453 fmap h (FromJSONKeyTextParser f) = FromJSONKeyTextParser (fmap h . f)
436 fmap h (FromJSONKeyValue f) = FromJSONKeyValue (fmap h . f)
454 fmap h (FromJSONKeyValue f) = FromJSONKeyValue (fmap h . f)
@@ -444,9 +462,7 @@ instance Functor FromJSONKeyFunction where
444--
462--
445-- On pre GHC 7.8 this is unconstrainted function.
463-- On pre GHC 7.8 this is unconstrainted function.
446fromJSONKeyCoerce ::
464fromJSONKeyCoerce ::
447−#if HAS_COERCIBLE
448 Coercible Text a =>
465 Coercible Text a =>
449−#endif
450 FromJSONKeyFunction a
466 FromJSONKeyFunction a
451fromJSONKeyCoerce = FromJSONKeyCoerce CoerceText
467fromJSONKeyCoerce = FromJSONKeyCoerce CoerceText
452
468
@@ -454,60 +470,91 @@ fromJSONKeyCoerce = FromJSONKeyCoerce CoerceText
454--
470--
455-- See note on 'fromJSONKeyCoerce'.
471-- See note on 'fromJSONKeyCoerce'.
456coerceFromJSONKeyFunction ::
472coerceFromJSONKeyFunction ::
457−#if HAS_COERCIBLE
458 Coercible a b =>
473 Coercible a b =>
459−#endif
460 FromJSONKeyFunction a -> FromJSONKeyFunction b
474 FromJSONKeyFunction a -> FromJSONKeyFunction b
461−#if HAS_COERCIBLE
462coerceFromJSONKeyFunction = coerce
475coerceFromJSONKeyFunction = coerce
463−#else
464−coerceFromJSONKeyFunction (FromJSONKeyCoerce CoerceText) = FromJSONKeyCoerce CoerceText
465−coerceFromJSONKeyFunction (FromJSONKeyText f) = FromJSONKeyText (coerce' . f)
466−coerceFromJSONKeyFunction (FromJSONKeyTextParser f) = FromJSONKeyTextParser (fmap coerce' . f)
467−coerceFromJSONKeyFunction (FromJSONKeyValue f) = FromJSONKeyValue (fmap coerce' . f)
468−#endif
469
476
470{-# RULES
477{-# RULES
471 "FromJSONKeyCoerce: fmap id" forall (x :: FromJSONKeyFunction a).
478 "FromJSONKeyCoerce: fmap id" forall (x :: FromJSONKeyFunction a).
472 fmap id x = x
479 fmap id x = x
473 #-}
480 #-}
474−#if HAS_COERCIBLE
475{-# RULES
481{-# RULES
476 "FromJSONKeyCoerce: fmap coerce" forall x .
482 "FromJSONKeyCoerce: fmap coerce" forall x .
477 fmap coerce x = coerceFromJSONKeyFunction x
483 fmap coerce x = coerceFromJSONKeyFunction x
478 #-}
484 #-}
479−#endif
480
485
481-- | Same as 'fmap'. Provided for the consistency with 'ToJSONKeyFunction'.
486-- | Same as 'fmap'. Provided for the consistency with 'ToJSONKeyFunction'.
482mapFromJSONKeyFunction :: (a -> b) -> FromJSONKeyFunction a -> FromJSONKeyFunction b
487mapFromJSONKeyFunction :: (a -> b) -> FromJSONKeyFunction a -> FromJSONKeyFunction b
483mapFromJSONKeyFunction = fmap
488mapFromJSONKeyFunction = fmap
484
489
490+-- | 'fromJSONKey' for 'Generic' types.
491+-- These types must be sums of nullary constructors, whose names will be used
492+-- as keys for JSON objects.
493+--
494+-- See also 'genericToJSONKey'.
495+--
496+-- === __Example__
497+--
498+-- @
499+-- data Color = Red | Green | Blue
500+-- deriving 'Generic'
501+--
502+-- instance 'FromJSONKey' Color where
503+-- 'fromJSONKey' = 'genericFromJSONKey' 'defaultJSONKeyOptions'
504+-- @
505+genericFromJSONKey :: forall a. (Generic a, GFromJSONKey (Rep a))
506+ => JSONKeyOptions
507+ -> FromJSONKeyFunction a
508+genericFromJSONKey opts = FromJSONKeyTextParser $ \t ->
509+ case parseSumFromString (keyModifier opts) t of
510+ Nothing -> fail $
511+ "invalid key " ++ show t ++ ", expected one of " ++ show cnames
512+ Just k -> pure (to k)
513+ where
514+ cnames = unTagged2 (constructorTags (keyModifier opts) :: Tagged2 (Rep a) [String])
515+
516+class (ConstructorNames f, SumFromString f) => GFromJSONKey f where
517+instance (ConstructorNames f, SumFromString f) => GFromJSONKey f where
518+
485-------------------------------------------------------------------------------
519-------------------------------------------------------------------------------
486-- Functions needed for documentation
520-- Functions needed for documentation
487-------------------------------------------------------------------------------
521-------------------------------------------------------------------------------
488
522
489-- | Fail parsing due to a type mismatch, with a descriptive message.
523-- | Fail parsing due to a type mismatch, with a descriptive message.
490--
524--
491−-- Example usage:
525+-- The following wrappers should generally be prefered:
526+-- 'withObject', 'withArray', 'withText', 'withBool'.
492--
527--
493−-- @
528+-- ==== Error message example
494−-- instance FromJSON Coord where
529+--
495−-- parseJSON ('Object' v) = {- type matches, life is good -}
530+-- > typeMismatch "Object" (String "oops")
496−-- parseJSON wat = 'typeMismatch' \"Coord\" wat
531+-- > -- Error: "expected Object, but encountered String"
497−-- @
498−typeMismatch :: String -- ^ The name of the type you are trying to parse.
532+typeMismatch :: String -- ^ The name of the JSON type being parsed
533+ -- (@\"Object\"@, @\"Array\"@, @\"String\"@, @\"Number\"@,
534+ -- @\"Boolean\"@, or @\"Null\"@).
499 -> Value -- ^ The actual value encountered.
535 -> Value -- ^ The actual value encountered.
500 -> Parser a
536 -> Parser a
501typeMismatch expected actual =
537typeMismatch expected actual =
502− fail $ "expected " ++ expected ++ ", encountered " ++ name
538+ fail $ "expected " ++ expected ++ ", but encountered " ++ typeOf actual
503− where
539+
540+-- | Fail parsing due to a type mismatch, when the expected types are implicit.
541+--
542+-- ==== Error message example
543+--
544+-- > unexpected (String "oops")
545+-- > -- Error: "unexpected String"
546+unexpected :: Value -> Parser a
547+unexpected actual = fail $ "unexpected " ++ typeOf actual
548+
549+-- | JSON type of a value, name of the head constructor.
550+typeOf :: Value -> String
504− name = case actual of
551+typeOf v = case v of
505− Object _ -> "Object"
552+ Object _ -> "Object"
506− Array _ -> "Array"
553+ Array _ -> "Array"
507− String _ -> "String"
554+ String _ -> "String"
508− Number _ -> "Number"
555+ Number _ -> "Number"
509− Bool _ -> "Boolean"
556+ Bool _ -> "Boolean"
510− Null -> "Null"
557+ Null -> "Null"
511
558
512-------------------------------------------------------------------------------
559-------------------------------------------------------------------------------
513-- Lifings of FromJSON and ToJSON to unary and binary type constructors
560-- Lifings of FromJSON and ToJSON to unary and binary type constructors
@@ -588,9 +635,8 @@ class FromJSON2 f where
588 -> (Value -> Parser b)
635 -> (Value -> Parser b)
589 -> (Value -> Parser [b])
636 -> (Value -> Parser [b])
590 -> Value -> Parser [f a b]
637 -> Value -> Parser [f a b]
591− liftParseJSONList2 fa ga fb gb v = case v of
638+ liftParseJSONList2 fa ga fb gb = withArray "[]" $ \vals ->
592− Array vals -> fmap V.toList (V.mapM (liftParseJSON2 fa ga fb gb) vals)
639+ fmap V.toList (V.mapM (liftParseJSON2 fa ga fb gb) vals)
593− _ -> typeMismatch "[a]" v
594
640
595-- | Lift the standard 'parseJSON' function through the type constructor.
641-- | Lift the standard 'parseJSON' function through the type constructor.
596parseJSON2 :: (FromJSON2 f, FromJSON a, FromJSON b) => Value -> Parser (f a b)
642parseJSON2 :: (FromJSON2 f, FromJSON a, FromJSON b) => Value -> Parser (f a b)
@@ -604,7 +650,7 @@ parseJSON2 = liftParseJSON2 parseJSON parseJSONList parseJSON parseJSONList
604-- | Helper function to use with 'liftParseJSON'. See 'Data.Aeson.ToJSON.listEncoding'.
650-- | Helper function to use with 'liftParseJSON'. See 'Data.Aeson.ToJSON.listEncoding'.
605listParser :: (Value -> Parser a) -> Value -> Parser [a]
651listParser :: (Value -> Parser a) -> Value -> Parser [a]
606listParser f (Array xs) = fmap V.toList (V.mapM f xs)
652listParser f (Array xs) = fmap V.toList (V.mapM f xs)
607−listParser _ v = typeMismatch "[a]" v
653+listParser _ v = typeMismatch "Array" v
608{-# INLINE listParser #-}
654{-# INLINE listParser #-}
609
655
610-------------------------------------------------------------------------------
656-------------------------------------------------------------------------------
@@ -622,49 +668,139 @@ instance (FromJSON a) => FromJSON [a] where
622-- Functions
668-- Functions
623-------------------------------------------------------------------------------
669-------------------------------------------------------------------------------
624
670
671+-- | Add context to a failure message, indicating the name of the structure
672+-- being parsed.
673+--
674+-- > prependContext "MyType" (fail "[error message]")
675+-- > -- Error: "parsing MyType failed, [error message]"
676+prependContext :: String -> Parser a -> Parser a
677+prependContext name = prependFailure ("parsing " ++ name ++ " failed, ")
678+
625−-- | @'withObject' expected f value@ applies @f@ to the 'Object' when @value@
679+-- | @'withObject' name f value@ applies @f@ to the 'Object' when @value@
626−-- is an 'Object' and fails using @'typeMismatch' expected@ otherwise.
680+-- is an 'Data.Aeson.Object' and fails otherwise.
681+--
682+-- ==== Error message example
683+--
684+-- > withObject "MyType" f (String "oops")
685+-- > -- Error: "parsing MyType failed, expected Object, but encountered String"
627withObject :: String -> (Object -> Parser a) -> Value -> Parser a
686withObject :: String -> (Object -> Parser a) -> Value -> Parser a
628−withObject _ f (Object obj) = f obj
687+withObject _ f (Object obj) = f obj
629−withObject expected _ v = typeMismatch expected v
688+withObject name _ v = prependContext name (typeMismatch "Object" v)
630{-# INLINE withObject #-}
689{-# INLINE withObject #-}
631
690
632−-- | @'withText' expected f value@ applies @f@ to the 'Text' when @value@ is a
691+-- | @'withText' name f value@ applies @f@ to the 'Text' when @value@ is a
633−-- 'String' and fails using @'typeMismatch' expected@ otherwise.
692+-- 'Data.Aeson.String' and fails otherwise.
693+--
694+-- ==== Error message example
695+--
696+-- > withText "MyType" f Null
697+-- > -- Error: "parsing MyType failed, expected String, but encountered Null"
634withText :: String -> (Text -> Parser a) -> Value -> Parser a
698withText :: String -> (Text -> Parser a) -> Value -> Parser a
635−withText _ f (String txt) = f txt
699+withText _ f (String txt) = f txt
636−withText expected _ v = typeMismatch expected v
700+withText name _ v = prependContext name (typeMismatch "String" v)
637{-# INLINE withText #-}
701{-# INLINE withText #-}
638
702
639-- | @'withArray' expected f value@ applies @f@ to the 'Array' when @value@ is
703-- | @'withArray' expected f value@ applies @f@ to the 'Array' when @value@ is
640−-- an 'Array' and fails using @'typeMismatch' expected@ otherwise.
704+-- an 'Data.Aeson.Array' and fails otherwise.
705+--
706+-- ==== Error message example
707+--
708+-- > withArray "MyType" f (String "oops")
709+-- > -- Error: "parsing MyType failed, expected Array, but encountered String"
641withArray :: String -> (Array -> Parser a) -> Value -> Parser a
710withArray :: String -> (Array -> Parser a) -> Value -> Parser a
642−withArray _ f (Array arr) = f arr
711+withArray _ f (Array arr) = f arr
643−withArray expected _ v = typeMismatch expected v
712+withArray name _ v = prependContext name (typeMismatch "Array" v)
644{-# INLINE withArray #-}
713{-# INLINE withArray #-}
645
714
646−-- | @'withNumber' expected f value@ applies @f@ to the 'Number' when @value@
647−-- is a 'Number' and fails using @'typeMismatch' expected@ otherwise.
648−withNumber :: String -> (Number -> Parser a) -> Value -> Parser a
649−withNumber expected f = withScientific expected (f . scientificToNumber)
650−{-# INLINE withNumber #-}
651−{-# DEPRECATED withNumber "Use withScientific instead" #-}
652−
653−-- | @'withScientific' expected f value@ applies @f@ to the 'Scientific' number
715+-- | @'withScientific' name f value@ applies @f@ to the 'Scientific' number
654−-- when @value@ is a 'Number' and fails using @'typeMismatch' expected@
716+-- when @value@ is a 'Data.Aeson.Number' and fails using 'typeMismatch'
655-- otherwise.
717-- otherwise.
718+--
719+-- /Warning/: If you are converting from a scientific to an unbounded
720+-- type such as 'Integer' you may want to add a restriction on the
721+-- size of the exponent (see 'withBoundedScientific') to prevent
722+-- malicious input from filling up the memory of the target system.
723+--
724+-- ==== Error message example
725+--
726+-- > withScientific "MyType" f (String "oops")
727+-- > -- Error: "parsing MyType failed, expected Number, but encountered String"
656withScientific :: String -> (Scientific -> Parser a) -> Value -> Parser a
728withScientific :: String -> (Scientific -> Parser a) -> Value -> Parser a
657−withScientific _ f (Number scientific) = f scientific
729+withScientific _ f (Number scientific) = f scientific
658−withScientific expected _ v = typeMismatch expected v
730+withScientific name _ v = prependContext name (typeMismatch "Number" v)
659{-# INLINE withScientific #-}
731{-# INLINE withScientific #-}
660
732
733+-- | A variant of 'withScientific' which doesn't use 'prependContext', so that
734+-- such context can be added separately in a way that also applies when the
735+-- continuation @f :: Scientific -> Parser a@ fails.
736+--
737+-- /Warning/: If you are converting from a scientific to an unbounded
738+-- type such as 'Integer' you may want to add a restriction on the
739+-- size of the exponent (see 'withBoundedScientific') to prevent
740+-- malicious input from filling up the memory of the target system.
741+--
742+-- ==== Error message examples
743+--
744+-- > withScientific' f (String "oops")
745+-- > -- Error: "unexpected String"
746+-- >
747+-- > prependContext "MyType" (withScientific' f (String "oops"))
748+-- > -- Error: "parsing MyType failed, unexpected String"
749+withScientific' :: (Scientific -> Parser a) -> Value -> Parser a
750+withScientific' f v = case v of
751+ Number n -> f n
752+ _ -> typeMismatch "Number" v
753+{-# INLINE withScientific' #-}
754+
755+-- | @'withBoundedScientific' name f value@ applies @f@ to the 'Scientific' number
756+-- when @value@ is a 'Number' with exponent less than or equal to 1024.
757+withBoundedScientific :: String -> (Scientific -> Parser a) -> Value -> Parser a
758+withBoundedScientific name f v = withBoundedScientific_ (prependContext name) f v
759+{-# INLINE withBoundedScientific #-}
760+
761+-- | A variant of 'withBoundedScientific' which doesn't use 'prependContext',
762+-- so that such context can be added separately in a way that also applies
763+-- when the continuation @f :: Scientific -> Parser a@ fails.
764+withBoundedScientific' :: (Scientific -> Parser a) -> Value -> Parser a
765+withBoundedScientific' f v = withBoundedScientific_ id f v
766+{-# INLINE withBoundedScientific' #-}
767+
768+-- | A variant of 'withBoundedScientific_' parameterized by a function to apply
769+-- to the 'Parser' in case of failure.
770+withBoundedScientific_ :: (Parser a -> Parser a) -> (Scientific -> Parser a) -> Value -> Parser a
771+withBoundedScientific_ whenFail f (Number scientific) =
772+ if exp10 > 1024
773+ then whenFail (fail msg)
774+ else f scientific
775+ where
776+ exp10 = base10Exponent scientific
777+ msg = "found a number with exponent " ++ show exp10 ++ ", but it must not be greater than 1024"
778+withBoundedScientific_ whenFail _ v =
779+ whenFail (typeMismatch "Number" v)
780+{-# INLINE withBoundedScientific_ #-}
781+
661-- | @'withBool' expected f value@ applies @f@ to the 'Bool' when @value@ is a
782-- | @'withBool' expected f value@ applies @f@ to the 'Bool' when @value@ is a
662−-- 'Bool' and fails using @'typeMismatch' expected@ otherwise.
783+-- 'Boolean' and fails otherwise.
784+--
785+-- ==== Error message example
786+--
787+-- > withBool "MyType" f (String "oops")
788+-- > -- Error: "parsing MyType failed, expected Boolean, but encountered String"
663withBool :: String -> (Bool -> Parser a) -> Value -> Parser a
789withBool :: String -> (Bool -> Parser a) -> Value -> Parser a
664−withBool _ f (Bool arr) = f arr
790+withBool _ f (Bool arr) = f arr
665−withBool expected _ v = typeMismatch expected v
791+withBool name _ v = prependContext name (typeMismatch "Boolean" v)
666{-# INLINE withBool #-}
792{-# INLINE withBool #-}
667
793
794+-- | Decode a nested JSON-encoded string.
795+withEmbeddedJSON :: String -> (Value -> Parser a) -> Value -> Parser a
796+withEmbeddedJSON _ innerParser (String txt) =
797+ either fail innerParser $ eitherDecode (L.fromStrict $ T.encodeUtf8 txt)
798+ where
799+ eitherDecode = eitherFormatError . eitherDecodeWith jsonEOF ifromJSON
800+ eitherFormatError = either (Left . uncurry formatError) Right
801+withEmbeddedJSON name _ v = prependContext name (typeMismatch "String" v)
802+{-# INLINE withEmbeddedJSON #-}
803+
668-- | Convert a value from JSON, failing if the types do not match.
804-- | Convert a value from JSON, failing if the types do not match.
669fromJSON :: (FromJSON a) => Value -> Result a
805fromJSON :: (FromJSON a) => Value -> Result a
670fromJSON = parse parseJSON
806fromJSON = parse parseJSON
@@ -727,7 +863,7 @@ parseFieldMaybe' = (.:!)
727-- E.g. @'explicitParseField' 'parseJSON1' :: ('FromJSON1' f, 'FromJSON' a) -> 'Object' -> 'Text' -> 'Parser' (f a)@
863-- E.g. @'explicitParseField' 'parseJSON1' :: ('FromJSON1' f, 'FromJSON' a) -> 'Object' -> 'Text' -> 'Parser' (f a)@
728explicitParseField :: (Value -> Parser a) -> Object -> Text -> Parser a
864explicitParseField :: (Value -> Parser a) -> Object -> Text -> Parser a
729explicitParseField p obj key = case H.lookup key obj of
865explicitParseField p obj key = case H.lookup key obj of
730− Nothing -> fail $ "key " ++ show key ++ " not present"
866+ Nothing -> fail $ "key " ++ show key ++ " not found"
731 Just v -> p v <?> Key key
867 Just v -> p v <?> Key key
732{-# INLINE explicitParseField #-}
868{-# INLINE explicitParseField #-}
733
869
@@ -767,11 +903,47 @@ pmval .!= val = fromMaybe val <$> pmval
767-- Generic parseJSON
903-- Generic parseJSON
768-------------------------------------------------------------------------------
904-------------------------------------------------------------------------------
769
905
906+instance GFromJSON arity V1 where
907+ -- Whereof we cannot format, thereof we cannot parse:
908+ gParseJSON _ _ _ = fail "Attempted to parse empty type"
909+
910+
770instance OVERLAPPABLE_ (GFromJSON arity a) => GFromJSON arity (M1 i c a) where
911instance OVERLAPPABLE_ (GFromJSON arity a) => GFromJSON arity (M1 i c a) where
771 -- Meta-information, which is not handled elsewhere, is just added to the
912 -- Meta-information, which is not handled elsewhere, is just added to the
772 -- parsed value:
913 -- parsed value:
773 gParseJSON opts fargs = fmap M1 . gParseJSON opts fargs
914 gParseJSON opts fargs = fmap M1 . gParseJSON opts fargs
774
915
916+-- Information for error messages
917+
918+type TypeName = String
919+type ConName = String
920+
921+-- | Add the name of the type being parsed to a parser's error messages.
922+contextType :: TypeName -> Parser a -> Parser a
923+contextType = prependContext
924+
925+-- | Add the tagKey that will be looked up while building an ADT
926+-- | Produce the error equivalent to
927+-- | Left "Error in $: parsing T failed, expected an object with keys "tag" and
928+-- | "contents", where "tag" i-- |s associated to one of ["Foo", "Bar"],
929+-- | The parser returned error was: could not find key "tag"
930+contextTag :: Text -> [String] -> Parser a -> Parser a
931+contextTag tagKey cnames = prependFailure
932+ ("expected Object with key \"" ++ unpack tagKey ++ "\"" ++
933+ " containing one of " ++ show cnames ++ ", ")
934+
935+-- | Add the name of the constructor being parsed to a parser's error messages.
936+contextCons :: ConName -> TypeName -> Parser a -> Parser a
937+contextCons cname tname = prependContext (showCons cname tname)
938+
939+-- | Render a constructor as @\"MyType(MyConstructor)\"@.
940+showCons :: ConName -> TypeName -> String
941+showCons cname tname = tname ++ "(" ++ cname ++ ")"
942+
943+--------------------------------------------------------------------------------
944+
945+-- Parsing single fields
946+
775instance (FromJSON a) => GFromJSON arity (K1 i a) where
947instance (FromJSON a) => GFromJSON arity (K1 i a) where
776 -- Constant values are decoded using their FromJSON instance:
948 -- Constant values are decoded using their FromJSON instance:
777 gParseJSON _opts _ = fmap K1 . parseJSON
949 gParseJSON _opts _ = fmap K1 . parseJSON
@@ -786,339 +958,499 @@ instance (FromJSON1 f) => GFromJSON One (Rec1 f) where
786 -- FromJSON1 instance:
958 -- FromJSON1 instance:
787 gParseJSON _opts (From1Args pj pjl) = fmap Rec1 . liftParseJSON pj pjl
959 gParseJSON _opts (From1Args pj pjl) = fmap Rec1 . liftParseJSON pj pjl
788
960
789−instance GFromJSON arity U1 where
961+instance (FromJSON1 f, GFromJSON One g) => GFromJSON One (f :.: g) where
790− -- Empty constructors are expected to be encoded as an empty array:
962+ -- If an occurrence of the last type parameter is nested inside two
963+ -- composed types, it is decoded by using the outermost type's FromJSON1
964+ -- instance to generically decode the innermost type:
791− gParseJSON _opts _ v
965+ gParseJSON opts fargs =
792− | isEmptyArray v = pure U1
966+ let gpj = gParseJSON opts fargs in
793− | otherwise = typeMismatch "unit constructor (U1)" v
967+ fmap Comp1 . liftParseJSON gpj (listParser gpj)
794
968
969+--------------------------------------------------------------------------------
970+
971+instance (GFromJSON' arity a, Datatype d) => GFromJSON arity (D1 d a) where
972+ -- Meta-information, which is not handled elsewhere, is just added to the
973+ -- parsed value:
974+ gParseJSON opts fargs = fmap M1 . gParseJSON' (tname :* opts :* fargs)
975+ where
976+ tname = moduleName proxy ++ "." ++ datatypeName proxy
977+ proxy = undefined :: M1 _i d _f _p
978+
979+-- | 'GFromJSON', after unwrapping the 'D1' constructor, now carrying the data
980+-- type's name.
981+class GFromJSON' arity f where
982+ gParseJSON' :: TypeName :* Options :* FromArgs arity a
983+ -> Value
984+ -> Parser (f a)
985+
986+-- | Single constructor.
795instance ( ConsFromJSON arity a
987instance ( ConsFromJSON arity a
796 , AllNullary (C1 c a) allNullary
988 , AllNullary (C1 c a) allNullary
797 , ParseSum arity (C1 c a) allNullary
989 , ParseSum arity (C1 c a) allNullary
990+ , Constructor c
798− ) => GFromJSON arity (D1 d (C1 c a)) where
991+ ) => GFromJSON' arity (C1 c a) where
799 -- The option 'tagSingleConstructors' determines whether to wrap
992 -- The option 'tagSingleConstructors' determines whether to wrap
800 -- a single-constructor type.
993 -- a single-constructor type.
801− gParseJSON opts fargs
994+ gParseJSON' p@(_ :* opts :* _)
802 | tagSingleConstructors opts
995 | tagSingleConstructors opts
803− = fmap M1
804− . (unTagged :: Tagged allNullary (Parser (C1 c a p)) -> Parser (C1 c a p))
996+ = (unTagged :: Tagged allNullary (Parser (C1 c a p)) -> Parser (C1 c a p))
805− . parseSum opts fargs
997+ . parseSum p
806− | otherwise = fmap M1 . fmap M1 . consParseJSON opts fargs
998+ | otherwise = fmap M1 . consParseJSON (cname :* p)
807−
999+ where
808−instance (ConsFromJSON arity a) => GFromJSON arity (C1 c a) where
1000+ cname = conName (undefined :: M1 _i c _f _p)
809− -- Constructors need to be decoded differently depending on whether they're
1001+
810− -- a record or not. This distinction is made by consParseJSON:
1002+-- | Multiple constructors.
811− gParseJSON opts fargs = fmap M1 . consParseJSON opts fargs
812−
813−instance ( FromProduct arity a, FromProduct arity b
814− , ProductSize a, ProductSize b
815− ) => GFromJSON arity (a :*: b) where
816− -- Products are expected to be encoded to an array. Here we check whether we
817− -- got an array of the same size as the product, then parse each of the
818− -- product's elements using parseProduct:
819− gParseJSON opts fargs = withArray "product (:*:)" $ \arr ->
820− let lenArray = V.length arr
821− lenProduct = (unTagged2 :: Tagged2 (a :*: b) Int -> Int)
822− productSize in
823− if lenArray == lenProduct
824− then parseProduct opts fargs arr 0 lenProduct
825− else fail $ "When expecting a product of " ++ show lenProduct ++
826− " values, encountered an Array of " ++ show lenArray ++
827− " elements instead"
828−
829−instance ( AllNullary (a :+: b) allNullary
1003+instance ( AllNullary (a :+: b) allNullary
830− , ParseSum arity (a :+: b) allNullary
1004+ , ParseSum arity (a :+: b) allNullary
831− ) => GFromJSON arity (a :+: b) where
1005+ ) => GFromJSON' arity (a :+: b) where
832 -- If all constructors of a sum datatype are nullary and the
1006 -- If all constructors of a sum datatype are nullary and the
833 -- 'allNullaryToStringTag' option is set they are expected to be
1007 -- 'allNullaryToStringTag' option is set they are expected to be
834 -- encoded as strings. This distinction is made by 'parseSum':
1008 -- encoded as strings. This distinction is made by 'parseSum':
835− gParseJSON opts fargs =
1009+ gParseJSON' p =
836− (unTagged :: Tagged allNullary (Parser ((a :+: b) d)) ->
1010+ (unTagged :: Tagged allNullary (Parser ((a :+: b) _d)) ->
837− Parser ((a :+: b) d))
1011+ Parser ((a :+: b) _d))
838− . parseSum opts fargs
1012+ . parseSum p
839−
840−instance (FromJSON1 f, GFromJSON One g) => GFromJSON One (f :.: g) where
841− -- If an occurrence of the last type parameter is nested inside two
842− -- composed types, it is decoded by using the outermost type's FromJSON1
843− -- instance to generically decode the innermost type:
844− gParseJSON opts fargs =
845− let gpj = gParseJSON opts fargs in
846− fmap Comp1 . liftParseJSON gpj (listParser gpj)
847
1013
848--------------------------------------------------------------------------------
1014--------------------------------------------------------------------------------
849
1015
850class ParseSum arity f allNullary where
1016class ParseSum arity f allNullary where
851− parseSum :: Options -> FromArgs arity a
1017+ parseSum :: TypeName :* Options :* FromArgs arity a
1018+ -> Value
852− -> Value -> Tagged allNullary (Parser (f a))
1019+ -> Tagged allNullary (Parser (f a))
853
1020
1021+instance ( ConstructorNames f
854−instance ( SumFromString f
1022+ , SumFromString f
855 , FromPair arity f
1023 , FromPair arity f
856 , FromTaggedObject arity f
1024 , FromTaggedObject arity f
857 , FromUntaggedValue arity f
1025 , FromUntaggedValue arity f
858 ) => ParseSum arity f True where
1026 ) => ParseSum arity f True where
859− parseSum opts fargs
1027+ parseSum p@(tname :* opts :* _)
860− | allNullaryToStringTag opts = Tagged . parseAllNullarySum opts
1028+ | allNullaryToStringTag opts = Tagged . parseAllNullarySum tname opts
861− | otherwise = Tagged . parseNonAllNullarySum opts fargs
1029+ | otherwise = Tagged . parseNonAllNullarySum p
862
1030
1031+instance ( ConstructorNames f
863−instance ( FromPair arity f
1032+ , FromPair arity f
864 , FromTaggedObject arity f
1033 , FromTaggedObject arity f
865 , FromUntaggedValue arity f
1034 , FromUntaggedValue arity f
866 ) => ParseSum arity f False where
1035 ) => ParseSum arity f False where
867− parseSum opts fargs = Tagged . parseNonAllNullarySum opts fargs
1036+ parseSum p = Tagged . parseNonAllNullarySum p
868
1037
869--------------------------------------------------------------------------------
1038--------------------------------------------------------------------------------
870
1039
871−parseAllNullarySum :: SumFromString f => Options -> Value -> Parser (f a)
1040+parseAllNullarySum :: (SumFromString f, ConstructorNames f)
1041+ => TypeName -> Options -> Value -> Parser (f a)
872−parseAllNullarySum opts = withText "Text" $ \key ->
1042+parseAllNullarySum tname opts =
1043+ withText tname $ \tag ->
873− maybe (notFound key) return $
1044+ maybe (badTag tag) return $
874− parseSumFromString opts key
1045+ parseSumFromString modifier tag
1046+ where
1047+ badTag tag = failWithCTags tname modifier $ \cnames ->
1048+ "expected one of the tags " ++ show cnames ++
1049+ ", but found tag " ++ show tag
1050+ modifier = constructorTagModifier opts
1051+
1052+-- | Fail with an informative error message about a mismatched tag.
1053+-- The error message is parameterized by the list of expected tags,
1054+-- to be inferred from the result type of the parser.
1055+failWithCTags
1056+ :: forall f a t. ConstructorNames f
1057+ => TypeName -> (String -> t) -> ([t] -> String) -> Parser (f a)
1058+failWithCTags tname modifier f =
1059+ contextType tname . fail $ f cnames
1060+ where
1061+ cnames = unTagged2 (constructorTags modifier :: Tagged2 f [t])
875
1062
876class SumFromString f where
1063class SumFromString f where
877− parseSumFromString :: Options -> Text -> Maybe (f a)
1064+ parseSumFromString :: (String -> String) -> Text -> Maybe (f a)
878
1065
879instance (SumFromString a, SumFromString b) => SumFromString (a :+: b) where
1066instance (SumFromString a, SumFromString b) => SumFromString (a :+: b) where
880 parseSumFromString opts key = (L1 <$> parseSumFromString opts key) <|>
1067 parseSumFromString opts key = (L1 <$> parseSumFromString opts key) <|>
881 (R1 <$> parseSumFromString opts key)
1068 (R1 <$> parseSumFromString opts key)
882
1069
883instance (Constructor c) => SumFromString (C1 c U1) where
1070instance (Constructor c) => SumFromString (C1 c U1) where
884− parseSumFromString opts key | key == name = Just $ M1 U1
1071+ parseSumFromString modifier key
1072+ | key == name = Just $ M1 U1
885− | otherwise = Nothing
1073+ | otherwise = Nothing
886− where
1074+ where
887− name = pack $ constructorTagModifier opts $
888− conName (undefined :: t c U1 p)
1075+ name = pack $ modifier $ conName (undefined :: M1 _i c _f _p)
889
1076
890−--------------------------------------------------------------------------------
1077+-- For genericFromJSONKey
1078+instance SumFromString a => SumFromString (D1 d a) where
1079+ parseSumFromString modifier key = M1 <$> parseSumFromString modifier key
1080+
1081+-- | List of all constructor tags.
1082+constructorTags :: ConstructorNames a => (String -> t) -> Tagged2 a [t]
1083+constructorTags modifier =
1084+ fmap DList.toList (constructorNames' modifier)
1085+
1086+-- | List of all constructor names of an ADT, after a given conversion
1087+-- function. (Better inlining.)
1088+class ConstructorNames a where
1089+ constructorNames' :: (String -> t) -> Tagged2 a (DList.DList t)
1090+
1091+instance (ConstructorNames a, ConstructorNames b) => ConstructorNames (a :+: b) where
1092+ constructorNames' = liftA2 append constructorNames' constructorNames'
1093+ where
1094+ append
1095+ :: Tagged2 a (DList.DList t)
1096+ -> Tagged2 b (DList.DList t)
1097+ -> Tagged2 (a :+: b) (DList.DList t)
1098+ append (Tagged2 xs) (Tagged2 ys) = Tagged2 (DList.append xs ys)
1099+
1100+instance Constructor c => ConstructorNames (C1 c a) where
1101+ constructorNames' f = Tagged2 (pure (f cname))
1102+ where
1103+ cname = conName (undefined :: M1 _i c _f _p)
1104+
1105+-- For genericFromJSONKey
1106+instance ConstructorNames a => ConstructorNames (D1 d a) where
1107+ constructorNames' = retag . constructorNames'
1108+ where
1109+ retag :: Tagged2 a u -> Tagged2 (D1 d a) u
1110+ retag (Tagged2 x) = Tagged2 x
891
1111
1112+--------------------------------------------------------------------------------
892−parseNonAllNullarySum :: ( FromPair arity f
1113+parseNonAllNullarySum :: forall f c arity.
1114+ ( FromPair arity f
893 , FromTaggedObject arity f
1115 , FromTaggedObject arity f
894 , FromUntaggedValue arity f
1116 , FromUntaggedValue arity f
1117+ , ConstructorNames f
895− ) => Options -> FromArgs arity c
1118+ ) => TypeName :* Options :* FromArgs arity c
896 -> Value -> Parser (f c)
1119 -> Value -> Parser (f c)
897−parseNonAllNullarySum opts fargs =
1120+parseNonAllNullarySum p@(tname :* opts :* _) =
898 case sumEncoding opts of
1121 case sumEncoding opts of
899 TaggedObject{..} ->
1122 TaggedObject{..} ->
900− withObject "Object" $ \obj -> do
1123+ withObject tname $ \obj -> do
901− tag <- obj .: pack tagFieldName
1124+ tag <- contextType tname . contextTag tagKey cnames_ $ obj .: tagKey
902− fromMaybe (notFound tag) $
1125+ fromMaybe (badTag tag <?> Key tagKey) $
903− parseFromTaggedObject opts fargs contentsFieldName obj tag
1126+ parseFromTaggedObject (tag :* contentsFieldName :* p) obj
1127+ where
1128+ tagKey = pack tagFieldName
1129+ badTag tag = failWith_ $ \cnames ->
1130+ "expected tag field to be one of " ++ show cnames ++
1131+ ", but found tag " ++ show tag
1132+ cnames_ = unTagged2 (constructorTags (constructorTagModifier opts) :: Tagged2 f [String])
904
1133
905 ObjectWithSingleField ->
1134 ObjectWithSingleField ->
906− withObject "Object" $ \obj ->
907− case H.toList obj of
1135+ withObject tname $ \obj -> case H.toList obj of
908− [pair@(tag, _)] -> fromMaybe (notFound tag) $
1136+ [(tag, v)] -> maybe (badTag tag) (<?> Key tag) $
909− parsePair opts fargs pair
1137+ parsePair (tag :* p) v
910− _ -> fail "Object doesn't have a single field"
1138+ _ -> contextType tname . fail $
1139+ "expected an Object with a single pair, but found " ++
1140+ show (H.size obj) ++ " pairs"
1141+ where
1142+ badTag tag = failWith_ $ \cnames ->
1143+ "expected an Object with a single pair where the tag is one of " ++
1144+ show cnames ++ ", but found tag " ++ show tag
911
1145
912 TwoElemArray ->
1146 TwoElemArray ->
913− withArray "Array" $ \arr ->
1147+ withArray tname $ \arr -> case V.length arr of
914− if V.length arr == 2
1148+ 2 | String tag <- V.unsafeIndex arr 0 ->
915− then case V.unsafeIndex arr 0 of
1149+ maybe (badTag tag <?> Index 0) (<?> Index 1) $
916− String tag -> fromMaybe (notFound tag) $
917− parsePair opts fargs (tag, V.unsafeIndex arr 1)
1150+ parsePair (tag :* p) (V.unsafeIndex arr 1)
1151+ | otherwise ->
1152+ contextType tname $
918− _ -> fail "First element is not a String"
1153+ fail "tag element is not a String" <?> Index 0
919− else fail "Array doesn't have 2 elements"
1154+ len -> contextType tname . fail $
1155+ "expected a 2-element Array, but encountered an Array of length " ++
1156+ show len
1157+ where
1158+ badTag tag = failWith_ $ \cnames ->
1159+ "expected tag of the 2-element Array to be one of " ++
1160+ show cnames ++ ", but found tag " ++ show tag
920
1161
921− UntaggedValue -> parseUntaggedValue opts fargs
1162+ UntaggedValue -> parseUntaggedValue p
1163+ where
1164+ failWith_ = failWithCTags tname (constructorTagModifier opts)
922
1165
923--------------------------------------------------------------------------------
1166--------------------------------------------------------------------------------
924
1167
925class FromTaggedObject arity f where
1168class FromTaggedObject arity f where
1169+ -- The first two components of the parameter tuple are: the constructor tag
1170+ -- to match against, and the contents field name.
926− parseFromTaggedObject :: Options -> FromArgs arity a
1171+ parseFromTaggedObject
1172+ :: Text :* String :* TypeName :* Options :* FromArgs arity a
927− -> String -> Object
1173+ -> Object
928− -> Text -> Maybe (Parser (f a))
1174+ -> Maybe (Parser (f a))
929
1175
930instance ( FromTaggedObject arity a, FromTaggedObject arity b) =>
1176instance ( FromTaggedObject arity a, FromTaggedObject arity b) =>
931 FromTaggedObject arity (a :+: b) where
1177 FromTaggedObject arity (a :+: b) where
932− parseFromTaggedObject opts fargs contentsFieldName obj tag =
1178+ parseFromTaggedObject p obj =
933− (fmap L1 <$> parseFromTaggedObject opts fargs contentsFieldName obj tag) <|>
1179+ (fmap L1 <$> parseFromTaggedObject p obj) <|>
934− (fmap R1 <$> parseFromTaggedObject opts fargs contentsFieldName obj tag)
1180+ (fmap R1 <$> parseFromTaggedObject p obj)
935
1181
1182+instance ( IsRecord f isRecord
936−instance ( FromTaggedObject' arity f
1183+ , FromTaggedObject' arity f isRecord
937 , Constructor c
1184 , Constructor c
938 ) => FromTaggedObject arity (C1 c f) where
1185 ) => FromTaggedObject arity (C1 c f) where
939− parseFromTaggedObject opts fargs contentsFieldName obj tag
1186+ parseFromTaggedObject (tag :* contentsFieldName :* p@(_ :* opts :* _))
1187+ | tag == tag'
1188+ = Just . fmap M1 .
1189+ (unTagged :: Tagged isRecord (Parser (f a)) -> Parser (f a)) .
940− | tag == name = Just $ M1 <$> parseFromTaggedObject'
1190+ parseFromTaggedObject' (contentsFieldName :* cname :* p)
941− opts fargs contentsFieldName obj
942− | otherwise = Nothing
1191+ | otherwise = const Nothing
943− where
1192+ where
944− name = pack $ constructorTagModifier opts $
1193+ tag' = pack $ constructorTagModifier opts cname
945− conName (undefined :: t c f p)
1194+ cname = conName (undefined :: M1 _i c _f _p)
946
1195
947--------------------------------------------------------------------------------
1196--------------------------------------------------------------------------------
948
1197
949−class FromTaggedObject' arity f where
1198+class FromTaggedObject' arity f isRecord where
950− parseFromTaggedObject' :: Options -> FromArgs arity a -> String
1199+ -- The first component of the parameter tuple is the contents field name.
951− -> Object -> Parser (f a)
1200+ parseFromTaggedObject'
952−
953−class FromTaggedObject'' arity f isRecord where
954− parseFromTaggedObject'' :: Options -> FromArgs arity a -> String
1201+ :: String :* ConName :* TypeName :* Options :* FromArgs arity a
955− -> Object -> Tagged isRecord (Parser (f a))
1202+ -> Object -> Tagged isRecord (Parser (f a))
956−
1203+
957−instance ( IsRecord f isRecord
958− , FromTaggedObject'' arity f isRecord
959− ) => FromTaggedObject' arity f where
1204+instance (RecordFromJSON arity f, FieldNames f) => FromTaggedObject' arity f True where
960− parseFromTaggedObject' opts fargs contentsFieldName =
1205+ -- Records are unpacked in the tagged object
961− (unTagged :: Tagged isRecord (Parser (f a)) -> Parser (f a)) .
962− parseFromTaggedObject'' opts fargs contentsFieldName
1206+ parseFromTaggedObject' (_ :* p) = Tagged . recordParseJSON (True :* p)
963−
1207+
964−instance (FromRecord arity f) => FromTaggedObject'' arity f True where
965− parseFromTaggedObject'' opts fargs _ =
966− Tagged . parseRecord opts fargs Nothing
967−
968−instance (GFromJSON arity f) => FromTaggedObject'' arity f False where
1208+instance (ConsFromJSON arity f) => FromTaggedObject' arity f False where
1209+ -- Nonnullary nonrecords are encoded in the contents field
969− parseFromTaggedObject'' opts fargs contentsFieldName = Tagged .
1210+ parseFromTaggedObject' p obj = Tagged $ do
1211+ contents <- contextCons cname tname (obj .: key)
1212+ consParseJSON p' contents <?> Key key
1213+ where
970− (gParseJSON opts fargs <=< (.: pack contentsFieldName))
1214+ key = pack contentsFieldName
1215+ contentsFieldName :* p'@(cname :* tname :* _) = p
971
1216
972−instance OVERLAPPING_ FromTaggedObject'' arity U1 False where
1217+instance OVERLAPPING_ FromTaggedObject' arity U1 False where
1218+ -- Nullary constructors don't need a contents field
973− parseFromTaggedObject'' _ _ _ _ = Tagged (pure U1)
1219+ parseFromTaggedObject' _ _ = Tagged (pure U1)
974
1220
975--------------------------------------------------------------------------------
1221--------------------------------------------------------------------------------
976
1222
1223+-- | Constructors need to be decoded differently depending on whether they're
1224+-- a record or not. This distinction is made by 'ConsParseJSON'.
977class ConsFromJSON arity f where
1225class ConsFromJSON arity f where
1226+ consParseJSON
978− consParseJSON :: Options -> FromArgs arity a
1227+ :: ConName :* TypeName :* Options :* FromArgs arity a
979− -> Value -> Parser (f a)
1228+ -> Value -> Parser (f a)
980
1229
981class ConsFromJSON' arity f isRecord where
1230class ConsFromJSON' arity f isRecord where
1231+ consParseJSON'
982− consParseJSON' :: Options -> FromArgs arity a
1232+ :: ConName :* TypeName :* Options :* FromArgs arity a
983− -> Maybe Text -- ^ A dummy label
984− -- (Nothing to use proper label)
985− -> Value -> Tagged isRecord (Parser (f a))
1233+ -> Value -> Tagged isRecord (Parser (f a))
986
1234
987instance ( IsRecord f isRecord
1235instance ( IsRecord f isRecord
988 , ConsFromJSON' arity f isRecord
1236 , ConsFromJSON' arity f isRecord
989 ) => ConsFromJSON arity f where
1237 ) => ConsFromJSON arity f where
990− consParseJSON opts fargs v = let
1238+ consParseJSON p =
991− (v2,lab) = case (unwrapUnaryRecords opts,isUnary (undefined :: f a)) of
992− -- use a dummy object with a dummy label
993− (True,True) -> (object [(pack "dummy",v)], Just $ pack "dummy")
994− _ ->(v,Nothing)
995− in (unTagged :: Tagged isRecord (Parser (f a)) -> Parser (f a))
1239+ (unTagged :: Tagged isRecord (Parser (f a)) -> Parser (f a))
996− $ consParseJSON' opts fargs lab v2
1240+ . consParseJSON' p
997
1241
1242+instance OVERLAPPING_
1243+ ( GFromJSON arity a, RecordFromJSON arity (S1 s a)
1244+ ) => ConsFromJSON' arity (S1 s a) True where
1245+ consParseJSON' p@(cname :* tname :* opts :* fargs)
1246+ | unwrapUnaryRecords opts = Tagged . fmap M1 . gParseJSON opts fargs
1247+ | otherwise = Tagged . withObject (showCons cname tname) (recordParseJSON (False :* p))
998
1248
999−instance (FromRecord arity f) => ConsFromJSON' arity f True where
1249+instance RecordFromJSON arity f => ConsFromJSON' arity f True where
1000− consParseJSON' opts fargs mlab = Tagged . withObject "record (:*:)"
1250+ consParseJSON' p@(cname :* tname :* _) =
1001− (parseRecord opts fargs mlab)
1251+ Tagged . withObject (showCons cname tname) (recordParseJSON (False :* p))
1002
1252
1253+instance OVERLAPPING_
1254+ ConsFromJSON' arity U1 False where
1255+ -- Empty constructors are expected to be encoded as an empty array:
1256+ consParseJSON' (cname :* tname :* _) v =
1257+ Tagged . contextCons cname tname $ case v of
1258+ Array a | V.null a -> pure U1
1259+ | otherwise -> fail_ a
1260+ _ -> typeMismatch "Array" v
1261+ where
1262+ fail_ a = fail $
1263+ "expected an empty Array, but encountered an Array of length " ++
1264+ show (V.length a)
1265+
1266+instance OVERLAPPING_
1003−instance (GFromJSON arity f) => ConsFromJSON' arity f False where
1267+ GFromJSON arity f => ConsFromJSON' arity (S1 s f) False where
1268+ consParseJSON' (_ :* _ :* opts :* fargs) =
1004− consParseJSON' opts fargs _ = Tagged . gParseJSON opts fargs
1269+ Tagged . fmap M1 . gParseJSON opts fargs
1270+
1271+instance (ProductFromJSON arity f, ProductSize f
1272+ ) => ConsFromJSON' arity f False where
1273+ consParseJSON' p = Tagged . productParseJSON0 p
1005
1274
1006--------------------------------------------------------------------------------
1275--------------------------------------------------------------------------------
1007
1276
1008−class FromRecord arity f where
1277+class FieldNames f where
1009− parseRecord :: Options -> FromArgs arity a
1278+ fieldNames :: f a -> [Text] -> [Text]
1010− -> Maybe Text -- ^ A dummy label
1011− -- (Nothing to use proper label)
1012− -> Object -> Parser (f a)
1013
1279
1014−instance ( FromRecord arity a
1280+instance (FieldNames a, FieldNames b) => FieldNames (a :*: b) where
1015− , FromRecord arity b
1281+ fieldNames _ =
1016− ) => FromRecord arity (a :*: b) where
1282+ fieldNames (undefined :: a x) .
1017− parseRecord opts fargs _ obj =
1283+ fieldNames (undefined :: b y)
1018− (:*:) <$> parseRecord opts fargs Nothing obj
1019− <*> parseRecord opts fargs Nothing obj
1020
1284
1021−instance OVERLAPPABLE_ (Selector s, GFromJSON arity a) =>
1285+instance (Selector s) => FieldNames (S1 s f) where
1022− FromRecord arity (S1 s a) where
1286+ fieldNames _ = (pack (selName (undefined :: M1 _i s _f _p)) :)
1023− parseRecord opts fargs lab =
1287+
1024− (<?> Key label) . gParseJSON opts fargs <=< (.: label)
1288+class RecordFromJSON arity f where
1289+ recordParseJSON
1290+ :: Bool :* ConName :* TypeName :* Options :* FromArgs arity a
1291+ -> Object -> Parser (f a)
1292+
1293+instance ( FieldNames f
1294+ , RecordFromJSON' arity f
1295+ ) => RecordFromJSON arity f where
1296+ recordParseJSON (fromTaggedSum :* p@(cname :* tname :* opts :* _)) =
1297+ \obj -> checkUnknown obj >> recordParseJSON' p obj
1025 where
1298 where
1026− label = fromMaybe defLabel lab
1299+ knownFields :: H.HashMap Text ()
1300+ knownFields = H.fromList $ map (,()) $
1301+ fieldNames (undefined :: f a)
1302+ [pack (tagFieldName (sumEncoding opts)) | fromTaggedSum]
1303+ checkUnknown =
1304+ if not (rejectUnknownFields opts)
1305+ then \_ -> return ()
1306+ else \obj -> case H.keys (H.difference obj knownFields) of
1307+ [] -> return ()
1308+ unknownFields -> contextCons cname tname $
1309+ fail ("unknown fields: " ++ show unknownFields)
1310+
1311+class RecordFromJSON' arity f where
1312+ recordParseJSON'
1313+ :: ConName :* TypeName :* Options :* FromArgs arity a
1314+ -> Object -> Parser (f a)
1315+
1316+instance ( RecordFromJSON' arity a
1317+ , RecordFromJSON' arity b
1318+ ) => RecordFromJSON' arity (a :*: b) where
1319+ recordParseJSON' p obj =
1320+ (:*:) <$> recordParseJSON' p obj
1321+ <*> recordParseJSON' p obj
1322+
1323+instance OVERLAPPABLE_ (Selector s, GFromJSON arity a) =>
1324+ RecordFromJSON' arity (S1 s a) where
1325+ recordParseJSON' (cname :* tname :* opts :* fargs) obj = do
1326+ fv <- contextCons cname tname (obj .: label)
1327+ M1 <$> gParseJSON opts fargs fv <?> Key label
1328+ where
1027− defLabel = pack . fieldLabelModifier opts $
1329+ label = pack $ fieldLabelModifier opts sname
1028− selName (undefined :: t s a p)
1330+ sname = selName (undefined :: M1 _i s _f _p)
1029
1331
1030instance INCOHERENT_ (Selector s, FromJSON a) =>
1332instance INCOHERENT_ (Selector s, FromJSON a) =>
1031− FromRecord arity (S1 s (K1 i (Maybe a))) where
1333+ RecordFromJSON' arity (S1 s (K1 i (Maybe a))) where
1032− parseRecord _ _ (Just lab) obj = (M1 . K1) <$> obj .:? lab
1334+ recordParseJSON' (_ :* _ :* opts :* _) obj = M1 . K1 <$> obj .:? pack label
1033− parseRecord opts _ Nothing obj = (M1 . K1) <$> obj .:? pack label
1034− where
1335+ where
1035− label = fieldLabelModifier opts $
1336+ label = fieldLabelModifier opts sname
1036− selName (undefined :: t s (K1 i (Maybe a)) p)
1337+ sname = selName (undefined :: M1 _i s _f _p)
1338+
1339+-- Parse an Option like a Maybe.
1340+instance INCOHERENT_ (Selector s, FromJSON a) =>
1341+ RecordFromJSON' arity (S1 s (K1 i (Semigroup.Option a))) where
1342+ recordParseJSON' p obj = wrap <$> recordParseJSON' p obj
1343+ where
1344+ wrap :: S1 s (K1 i (Maybe a)) p -> S1 s (K1 i (Semigroup.Option a)) p
1345+ wrap (M1 (K1 a)) = M1 (K1 (Semigroup.Option a))
1037
1346
1038--------------------------------------------------------------------------------
1347--------------------------------------------------------------------------------
1039
1348
1349+productParseJSON0
1350+ :: forall f arity a. (ProductFromJSON arity f, ProductSize f)
1351+ => ConName :* TypeName :* Options :* FromArgs arity a
1352+ -> Value -> Parser (f a)
1353+ -- Products are expected to be encoded to an array. Here we check whether we
1354+ -- got an array of the same size as the product, then parse each of the
1355+ -- product's elements using productParseJSON:
1356+productParseJSON0 p@(cname :* tname :* _ :* _) =
1357+ withArray (showCons cname tname) $ \arr ->
1358+ let lenArray = V.length arr
1359+ lenProduct = (unTagged2 :: Tagged2 f Int -> Int)
1360+ productSize in
1361+ if lenArray == lenProduct
1362+ then productParseJSON p arr 0 lenProduct
1363+ else contextCons cname tname $
1364+ fail $ "expected an Array of length " ++ show lenProduct ++
1365+ ", but encountered an Array of length " ++ show lenArray
1366+
1367+--
1368+
1040−class FromProduct arity f where
1369+class ProductFromJSON arity f where
1041− parseProduct :: Options -> FromArgs arity a
1370+ productParseJSON :: ConName :* TypeName :* Options :* FromArgs arity a
1042 -> Array -> Int -> Int
1371 -> Array -> Int -> Int
1043 -> Parser (f a)
1372 -> Parser (f a)
1044
1373
1045−instance ( FromProduct arity a
1374+instance ( ProductFromJSON arity a
1046− , FromProduct arity b
1375+ , ProductFromJSON arity b
1047− ) => FromProduct arity (a :*: b) where
1376+ ) => ProductFromJSON arity (a :*: b) where
1048− parseProduct opts fargs arr ix len =
1377+ productParseJSON p arr ix len =
1049− (:*:) <$> parseProduct opts fargs arr ix lenL
1378+ (:*:) <$> productParseJSON p arr ix lenL
1050− <*> parseProduct opts fargs arr ixR lenR
1379+ <*> productParseJSON p arr ixR lenR
1051 where
1380 where
1052 lenL = len `unsafeShiftR` 1
1381 lenL = len `unsafeShiftR` 1
1053 ixR = ix + lenL
1382 ixR = ix + lenL
1054 lenR = len - lenL
1383 lenR = len - lenL
1055
1384
1056−instance (GFromJSON arity a) => FromProduct arity (S1 s a) where
1385+instance (GFromJSON arity a) => ProductFromJSON arity (S1 s a) where
1057− parseProduct opts fargs arr ix _ =
1386+ productParseJSON (_ :* _ :* opts :* fargs) arr ix _ =
1058− gParseJSON opts fargs $ V.unsafeIndex arr ix
1387+ M1 <$> gParseJSON opts fargs (V.unsafeIndex arr ix) <?> Index ix
1059
1388
1060--------------------------------------------------------------------------------
1389--------------------------------------------------------------------------------
1061
1390
1062class FromPair arity f where
1391class FromPair arity f where
1392+ -- The first component of the parameter tuple is the tag to match.
1063− parsePair :: Options -> FromArgs arity a
1393+ parsePair :: Text :* TypeName :* Options :* FromArgs arity a
1394+ -> Value
1064− -> Pair -> Maybe (Parser (f a))
1395+ -> Maybe (Parser (f a))
1065
1396
1066instance ( FromPair arity a
1397instance ( FromPair arity a
1067 , FromPair arity b
1398 , FromPair arity b
1068 ) => FromPair arity (a :+: b) where
1399 ) => FromPair arity (a :+: b) where
1400+ parsePair p pair =
1069− parsePair opts fargs pair = (fmap L1 <$> parsePair opts fargs pair) <|>
1401+ (fmap L1 <$> parsePair p pair) <|>
1070− (fmap R1 <$> parsePair opts fargs pair)
1402+ (fmap R1 <$> parsePair p pair)
1071
1403
1072instance ( Constructor c
1404instance ( Constructor c
1073− , GFromJSON arity a
1074 , ConsFromJSON arity a
1405 , ConsFromJSON arity a
1075 ) => FromPair arity (C1 c a) where
1406 ) => FromPair arity (C1 c a) where
1076− parsePair opts fargs (tag, value)
1407+ parsePair (tag :* p@(_ :* opts :* _)) v
1077− | tag == tag' = Just $ gParseJSON opts fargs value
1408+ | tag == tag' = Just $ M1 <$> consParseJSON (cname :* p) v
1078 | otherwise = Nothing
1409 | otherwise = Nothing
1079− where
1410+ where
1080− tag' = pack $ constructorTagModifier opts $
1411+ tag' = pack $ constructorTagModifier opts cname
1081− conName (undefined :: t c a p)
1412+ cname = conName (undefined :: M1 _i c _a _p)
1082
1413
1083--------------------------------------------------------------------------------
1414--------------------------------------------------------------------------------
1084
1415
1085class FromUntaggedValue arity f where
1416class FromUntaggedValue arity f where
1086− parseUntaggedValue :: Options -> FromArgs arity a
1417+ parseUntaggedValue :: TypeName :* Options :* FromArgs arity a
1418+ -> Value
1087− -> Value -> Parser (f a)
1419+ -> Parser (f a)
1088
1420
1089instance
1421instance
1090 ( FromUntaggedValue arity a
1422 ( FromUntaggedValue arity a
1091 , FromUntaggedValue arity b
1423 , FromUntaggedValue arity b
1092 ) => FromUntaggedValue arity (a :+: b)
1424 ) => FromUntaggedValue arity (a :+: b)
1093 where
1425 where
1094− parseUntaggedValue opts fargs value =
1426+ parseUntaggedValue p value =
1095− L1 <$> parseUntaggedValue opts fargs value <|>
1427+ L1 <$> parseUntaggedValue p value <|>
1096− R1 <$> parseUntaggedValue opts fargs value
1428+ R1 <$> parseUntaggedValue p value
1097
1429
1098instance OVERLAPPABLE_
1430instance OVERLAPPABLE_
1099− ( GFromJSON arity a
1431+ ( ConsFromJSON arity a
1100− , ConsFromJSON arity a
1432+ , Constructor c
1101 ) => FromUntaggedValue arity (C1 c a)
1433 ) => FromUntaggedValue arity (C1 c a)
1102 where
1434 where
1103− parseUntaggedValue = gParseJSON
1435+ parseUntaggedValue p = fmap M1 . consParseJSON (cname :* p)
1436+ where
1437+ cname = conName (undefined :: M1 _i c _f _p)
1104
1438
1105instance OVERLAPPING_
1439instance OVERLAPPING_
1106 ( Constructor c )
1440 ( Constructor c )
1107 => FromUntaggedValue arity (C1 c U1)
1441 => FromUntaggedValue arity (C1 c U1)
1108 where
1442 where
1109− parseUntaggedValue opts _ (String s)
1443+ parseUntaggedValue (tname :* opts :* _) v =
1110− | s == pack (constructorTagModifier opts (conName (undefined :: t c U1 p))) =
1444+ contextCons cname tname $ case v of
1445+ String tag
1111− pure $ M1 U1
1446+ | tag == tag' -> pure $ M1 U1
1112− | otherwise =
1447+ | otherwise -> fail_ tag
1113− fail $ "Invalid tag: " ++ unpack s
1448+ _ -> typeMismatch "String" v
1114− parseUntaggedValue _ _ v = typeMismatch (conName (undefined :: t c U1 p)) v
1449+ where
1115−
1450+ tag' = pack $ constructorTagModifier opts cname
1116−
1451+ cname = conName (undefined :: M1 _i c _f _p)
1117−--------------------------------------------------------------------------------
1452+ fail_ tag = fail $
1118−
1453+ "expected tag " ++ show tag' ++ ", but found tag " ++ show tag
1119−notFound :: Text -> Parser a
1120−notFound key = fail $ "The key \"" ++ unpack key ++ "\" was not found"
1121−{-# INLINE notFound #-}
1122
1454
1123-------------------------------------------------------------------------------
1455-------------------------------------------------------------------------------
1124-- Instances
1456-- Instances
@@ -1176,17 +1508,20 @@ instance (FromJSON a, FromJSON b) => FromJSON (Either a b) where
1176 parseJSON = parseJSON2
1508 parseJSON = parseJSON2
1177 {-# INLINE parseJSON #-}
1509 {-# INLINE parseJSON #-}
1178
1510
1179−
1511+instance FromJSON Void where
1512+ parseJSON _ = fail "Cannot parse Void"
1513+ {-# INLINE parseJSON #-}
1180
1514
1181instance FromJSON Bool where
1515instance FromJSON Bool where
1182− parseJSON = withBool "Bool" pure
1516+ parseJSON (Bool b) = pure b
1517+ parseJSON v = typeMismatch "Bool" v
1183 {-# INLINE parseJSON #-}
1518 {-# INLINE parseJSON #-}
1184
1519
1185instance FromJSONKey Bool where
1520instance FromJSONKey Bool where
1186 fromJSONKey = FromJSONKeyTextParser $ \t -> case t of
1521 fromJSONKey = FromJSONKeyTextParser $ \t -> case t of
1187 "true" -> pure True
1522 "true" -> pure True
1188 "false" -> pure False
1523 "false" -> pure False
1189− _ -> fail $ "Cannot parse key into Bool: " ++ T.unpack t
1524+ _ -> fail $ "cannot parse key " ++ show t ++ " into Bool"
1190
1525
1191instance FromJSON Ordering where
1526instance FromJSON Ordering where
1192 parseJSON = withText "Ordering" $ \s ->
1527 parseJSON = withText "Ordering" $ \s ->
@@ -1194,25 +1529,30 @@ instance FromJSON Ordering where
1194 "LT" -> return LT
1529 "LT" -> return LT
1195 "EQ" -> return EQ
1530 "EQ" -> return EQ
1196 "GT" -> return GT
1531 "GT" -> return GT
1532+ _ -> fail $ "parsing Ordering failed, unexpected " ++ show s ++
1197− _ -> fail "Parsing Ordering value failed: expected \"LT\", \"EQ\", or \"GT\""
1533+ " (expected \"LT\", \"EQ\", or \"GT\")"
1198
1534
1199instance FromJSON () where
1535instance FromJSON () where
1200 parseJSON = withArray "()" $ \v ->
1536 parseJSON = withArray "()" $ \v ->
1201 if V.null v
1537 if V.null v
1202 then pure ()
1538 then pure ()
1203− else fail "Expected an empty array"
1539+ else prependContext "()" $ fail "expected an empty array"
1204 {-# INLINE parseJSON #-}
1540 {-# INLINE parseJSON #-}
1205
1541
1206instance FromJSON Char where
1542instance FromJSON Char where
1207− parseJSON = withText "Char" $ \t ->
1543+ parseJSON = withText "Char" parseChar
1208− if T.compareLength t 1 == EQ
1209− then pure $ T.head t
1210− else fail "Expected a string of length 1"
1211 {-# INLINE parseJSON #-}
1544 {-# INLINE parseJSON #-}
1212
1545
1213− parseJSONList = withText "String" $ pure . T.unpack
1546+ parseJSONList (String s) = pure (T.unpack s)
1547+ parseJSONList v = typeMismatch "String" v
1214 {-# INLINE parseJSONList #-}
1548 {-# INLINE parseJSONList #-}
1215
1549
1550+parseChar :: Text -> Parser Char
1551+parseChar t =
1552+ if T.compareLength t 1 == EQ
1553+ then pure $ T.head t
1554+ else prependContext "Char" $ fail "expected a string of length 1"
1555+
1216instance FromJSON Double where
1556instance FromJSON Double where
1217 parseJSON = parseRealFloat "Double"
1557 parseJSON = parseRealFloat "Double"
1218 {-# INLINE parseJSON #-}
1558 {-# INLINE parseJSON #-}
@@ -1224,12 +1564,6 @@ instance FromJSONKey Double where
1224 "-Infinity" -> pure (negate 1/0)
1564 "-Infinity" -> pure (negate 1/0)
1225 _ -> Scientific.toRealFloat <$> parseScientificText t
1565 _ -> Scientific.toRealFloat <$> parseScientificText t
1226
1566
1227−instance FromJSON Number where
1228− parseJSON (Number s) = pure $ scientificToNumber s
1229− parseJSON Null = pure (D (0/0))
1230− parseJSON v = typeMismatch "Number" v
1231− {-# INLINE parseJSON #-}
1232−
1233instance FromJSON Float where
1567instance FromJSON Float where
1234 parseJSON = parseRealFloat "Float"
1568 parseJSON = parseRealFloat "Float"
1235 {-# INLINE parseJSON #-}
1569 {-# INLINE parseJSON #-}
@@ -1242,17 +1576,30 @@ instance FromJSONKey Float where
1242 _ -> Scientific.toRealFloat <$> parseScientificText t
1576 _ -> Scientific.toRealFloat <$> parseScientificText t
1243
1577
1244instance (FromJSON a, Integral a) => FromJSON (Ratio a) where
1578instance (FromJSON a, Integral a) => FromJSON (Ratio a) where
1579+ parseJSON (Number x)
1580+ | exp10 <= 1024
1581+ , exp10 >= -1024 = return $! realToFrac x
1582+ | otherwise = prependContext "Ratio" $ fail msg
1583+ where
1584+ exp10 = base10Exponent x
1585+ msg = "found a number with exponent " ++ show exp10
1586+ ++ ", but it must not be greater than 1024 or less than -1024"
1587+ parseJSON o = objParser o
1588+ where
1245− parseJSON = withObject "Rational" $ \obj ->
1589+ objParser = withObject "Rational" $ \obj -> do
1246− (%) <$> obj .: "numerator"
1590+ numerator <- obj .: "numerator"
1591+ denominator <- obj .: "denominator"
1592+ if denominator == 0
1593+ then fail "Ratio denominator was 0"
1247− <*> obj .: "denominator"
1594+ else pure $ numerator % denominator
1248− {-# INLINE parseJSON #-}
1595+ {-# INLINE parseJSON #-}
1249−
1596+
1250−-- | /WARNING:/ Only parse fixed-precision numbers from trusted input
1597+-- | This instance includes a bounds check to prevent maliciously
1251−-- since an attacker could easily fill up the memory of the target
1598+-- large inputs to fill up the memory of the target system. You can
1252−-- system by specifying a scientific number with a big exponent like
1599+-- newtype 'Scientific' and provide your own instance using
1253−-- @1e1000000000@.
1600+-- 'withScientific' if you want to allow larger inputs.
1254instance HasResolution a => FromJSON (Fixed a) where
1601instance HasResolution a => FromJSON (Fixed a) where
1255− parseJSON = withScientific "Fixed" $ pure . realToFrac
1602+ parseJSON = prependContext "Fixed" . withBoundedScientific' (pure . realToFrac)
1256 {-# INLINE parseJSON #-}
1603 {-# INLINE parseJSON #-}
1257
1604
1258instance FromJSON Int where
1605instance FromJSON Int where
@@ -1262,10 +1609,10 @@ instance FromJSON Int where
1262instance FromJSONKey Int where
1609instance FromJSONKey Int where
1263 fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Int"
1610 fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Int"
1264
1611
1265−-- | /WARNING:/ Only parse Integers from trusted input since an
1612+-- | This instance includes a bounds check to prevent maliciously
1266−-- attacker could easily fill up the memory of the target system by
1613+-- large inputs to fill up the memory of the target system. You can
1267−-- specifying a scientific number with a big exponent like
1614+-- newtype 'Scientific' and provide your own instance using
1268−-- @1e1000000000@.
1615+-- 'withScientific' if you want to allow larger inputs.
1269instance FromJSON Integer where
1616instance FromJSON Integer where
1270 parseJSON = parseIntegral "Integer"
1617 parseJSON = parseIntegral "Integer"
1271 {-# INLINE parseJSON #-}
1618 {-# INLINE parseJSON #-}
@@ -1275,19 +1622,20 @@ instance FromJSONKey Integer where
1275
1622
1276instance FromJSON Natural where
1623instance FromJSON Natural where
1277 parseJSON value = do
1624 parseJSON value = do
1278− integer :: Integer <- parseIntegral "Natural" value
1625+ integer <- parseIntegral "Natural" value
1279− if integer < 0 then
1626+ parseNatural integer
1280− fail $ "expected Natural, encountered negative number " <> show integer
1281− else
1282− pure $ fromIntegral integer
1283
1627
1284instance FromJSONKey Natural where
1628instance FromJSONKey Natural where
1285 fromJSONKey = FromJSONKeyTextParser $ \text -> do
1629 fromJSONKey = FromJSONKeyTextParser $ \text -> do
1286− integer :: Integer <- parseIntegralText "Natural" text
1630+ integer <- parseIntegralText "Natural" text
1631+ parseNatural integer
1632+
1633+parseNatural :: Integer -> Parser Natural
1634+parseNatural integer =
1287− if integer < 0 then
1635+ if integer < 0 then
1288− fail $ "expected Natural, encountered negative number " <> show integer
1636+ fail $ "parsing Natural failed, unexpected negative number " <> show integer
1289− else
1637+ else
1290− pure $ fromIntegral integer
1638+ pure $ fromIntegral integer
1291
1639
1292instance FromJSON Int8 where
1640instance FromJSON Int8 where
1293 parseJSON = parseBoundedIntegral "Int8"
1641 parseJSON = parseBoundedIntegral "Int8"
@@ -1384,17 +1732,17 @@ parseVersionText = go . readP_to_S parseVersion . unpack
1384 where
1732 where
1385 go [(v,[])] = return v
1733 go [(v,[])] = return v
1386 go (_ : xs) = go xs
1734 go (_ : xs) = go xs
1387− go _ = fail "could not parse Version"
1735+ go _ = fail "parsing Version failed"
1388
1736
1389-------------------------------------------------------------------------------
1737-------------------------------------------------------------------------------
1390-- semigroups NonEmpty
1738-- semigroups NonEmpty
1391-------------------------------------------------------------------------------
1739-------------------------------------------------------------------------------
1392
1740
1393instance FromJSON1 NonEmpty where
1741instance FromJSON1 NonEmpty where
1394− liftParseJSON p _ = withArray "NonEmpty a" $
1742+ liftParseJSON p _ = withArray "NonEmpty" $
1395 (>>= ne) . Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1743 (>>= ne) . Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1396 where
1744 where
1397− ne [] = fail "Expected a NonEmpty but got an empty list"
1745+ ne [] = fail "parsing NonEmpty failed, unexpected empty list"
1398 ne (x:xs) = pure (x :| xs)
1746 ne (x:xs) = pure (x :| xs)
1399 {-# INLINE liftParseJSON #-}
1747 {-# INLINE liftParseJSON #-}
1400
1748
@@ -1415,7 +1763,7 @@ instance FromJSON Scientific where
1415-------------------------------------------------------------------------------
1763-------------------------------------------------------------------------------
1416
1764
1417instance FromJSON1 DList.DList where
1765instance FromJSON1 DList.DList where
1418− liftParseJSON p _ = withArray "DList a" $
1766+ liftParseJSON p _ = withArray "DList" $
1419 fmap DList.fromList .
1767 fmap DList.fromList .
1420 Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1768 Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1421 {-# INLINE liftParseJSON #-}
1769 {-# INLINE liftParseJSON #-}
@@ -1492,7 +1840,7 @@ instance (FromJSON1 f, FromJSON1 g) => FromJSON1 (Sum f g) where
1492 inr = "InR"
1840 inr = "InR"
1493
1841
1494 liftParseJSON _ _ _ = fail $
1842 liftParseJSON _ _ _ = fail $
1495− "expected an object with a single property " ++
1843+ "parsing Sum failed, expected an object with a single property " ++
1496 "where the property key should be either " ++
1844 "where the property key should be either " ++
1497 "\"InL\" or \"InR\""
1845 "\"InL\" or \"InR\""
1498 {-# INLINE liftParseJSON #-}
1846 {-# INLINE liftParseJSON #-}
@@ -1506,7 +1854,7 @@ instance (FromJSON1 f, FromJSON1 g, FromJSON a) => FromJSON (Sum f g a) where
1506-------------------------------------------------------------------------------
1854-------------------------------------------------------------------------------
1507
1855
1508instance FromJSON1 Seq.Seq where
1856instance FromJSON1 Seq.Seq where
1509− liftParseJSON p _ = withArray "Seq a" $
1857+ liftParseJSON p _ = withArray "Seq" $
1510 fmap Seq.fromList .
1858 fmap Seq.fromList .
1511 Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1859 Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1512 {-# INLINE liftParseJSON #-}
1860 {-# INLINE liftParseJSON #-}
@@ -1540,13 +1888,13 @@ instance FromJSON a => FromJSON (IntMap.IntMap a) where
1540
1888
1541instance (FromJSONKey k, Ord k) => FromJSON1 (M.Map k) where
1889instance (FromJSONKey k, Ord k) => FromJSON1 (M.Map k) where
1542 liftParseJSON p _ = case fromJSONKey of
1890 liftParseJSON p _ = case fromJSONKey of
1543− FromJSONKeyCoerce _-> withObject "Map k v" $
1891+ FromJSONKeyCoerce _ -> withObject "Map" $
1544 fmap (H.foldrWithKey (M.insert . unsafeCoerce) M.empty) . H.traverseWithKey (\k v -> p v <?> Key k)
1892 fmap (H.foldrWithKey (M.insert . unsafeCoerce) M.empty) . H.traverseWithKey (\k v -> p v <?> Key k)
1545− FromJSONKeyText f -> withObject "Map k v" $
1893+ FromJSONKeyText f -> withObject "Map" $
1546 fmap (H.foldrWithKey (M.insert . f) M.empty) . H.traverseWithKey (\k v -> p v <?> Key k)
1894 fmap (H.foldrWithKey (M.insert . f) M.empty) . H.traverseWithKey (\k v -> p v <?> Key k)
1547− FromJSONKeyTextParser f -> withObject "Map k v" $
1895+ FromJSONKeyTextParser f -> withObject "Map" $
1548 H.foldrWithKey (\k v m -> M.insert <$> f k <?> Key k <*> p v <?> Key k <*> m) (pure M.empty)
1896 H.foldrWithKey (\k v m -> M.insert <$> f k <?> Key k <*> p v <?> Key k <*> m) (pure M.empty)
1549− FromJSONKeyValue f -> withArray "Map k v" $ \arr ->
1897+ FromJSONKeyValue f -> withArray "Map" $ \arr ->
1550 fmap M.fromList . Tr.sequence .
1898 fmap M.fromList . Tr.sequence .
1551 zipWith (parseIndexedJSONPair f p) [0..] . V.toList $ arr
1899 zipWith (parseIndexedJSONPair f p) [0..] . V.toList $ arr
1552 {-# INLINE liftParseJSON #-}
1900 {-# INLINE liftParseJSON #-}
@@ -1574,18 +1922,18 @@ instance (FromJSON v) => FromJSON (Tree.Tree v) where
1574
1922
1575instance FromJSON UUID.UUID where
1923instance FromJSON UUID.UUID where
1576 parseJSON = withText "UUID" $
1924 parseJSON = withText "UUID" $
1577− maybe (fail "Invalid UUID") pure . UUID.fromText
1925+ maybe (fail "invalid UUID") pure . UUID.fromText
1578
1926
1579instance FromJSONKey UUID.UUID where
1927instance FromJSONKey UUID.UUID where
1580 fromJSONKey = FromJSONKeyTextParser $
1928 fromJSONKey = FromJSONKeyTextParser $
1581− maybe (fail "Invalid UUID") pure . UUID.fromText
1929+ maybe (fail "invalid UUID") pure . UUID.fromText
1582
1930
1583-------------------------------------------------------------------------------
1931-------------------------------------------------------------------------------
1584-- vector
1932-- vector
1585-------------------------------------------------------------------------------
1933-------------------------------------------------------------------------------
1586
1934
1587instance FromJSON1 Vector where
1935instance FromJSON1 Vector where
1588− liftParseJSON p _ = withArray "Vector a" $
1936+ liftParseJSON p _ = withArray "Vector" $
1589 V.mapM (uncurry $ parseIndexedJSON p) . V.indexed
1937 V.mapM (uncurry $ parseIndexedJSON p) . V.indexed
1590 {-# INLINE liftParseJSON #-}
1938 {-# INLINE liftParseJSON #-}
1591
1939
@@ -1599,14 +1947,14 @@ vectorParseJSON s = withArray s $ fmap V.convert . V.mapM (uncurry $ parseIndexe
1599{-# INLINE vectorParseJSON #-}
1947{-# INLINE vectorParseJSON #-}
1600
1948
1601instance (Storable a, FromJSON a) => FromJSON (VS.Vector a) where
1949instance (Storable a, FromJSON a) => FromJSON (VS.Vector a) where
1602− parseJSON = vectorParseJSON "Data.Vector.Storable.Vector a"
1950+ parseJSON = vectorParseJSON "Data.Vector.Storable.Vector"
1603
1951
1604instance (VP.Prim a, FromJSON a) => FromJSON (VP.Vector a) where
1952instance (VP.Prim a, FromJSON a) => FromJSON (VP.Vector a) where
1605− parseJSON = vectorParseJSON "Data.Vector.Primitive.Vector a"
1953+ parseJSON = vectorParseJSON "Data.Vector.Primitive.Vector"
1606 {-# INLINE parseJSON #-}
1954 {-# INLINE parseJSON #-}
1607
1955
1608instance (VG.Vector VU.Vector a, FromJSON a) => FromJSON (VU.Vector a) where
1956instance (VG.Vector VU.Vector a, FromJSON a) => FromJSON (VU.Vector a) where
1609− parseJSON = vectorParseJSON "Data.Vector.Unboxed.Vector a"
1957+ parseJSON = vectorParseJSON "Data.Vector.Unboxed.Vector"
1610 {-# INLINE parseJSON #-}
1958 {-# INLINE parseJSON #-}
1611
1959
1612-------------------------------------------------------------------------------
1960-------------------------------------------------------------------------------
@@ -1620,13 +1968,13 @@ instance (Eq a, Hashable a, FromJSON a) => FromJSON (HashSet.HashSet a) where
1620
1968
1621instance (FromJSONKey k, Eq k, Hashable k) => FromJSON1 (H.HashMap k) where
1969instance (FromJSONKey k, Eq k, Hashable k) => FromJSON1 (H.HashMap k) where
1622 liftParseJSON p _ = case fromJSONKey of
1970 liftParseJSON p _ = case fromJSONKey of
1623− FromJSONKeyCoerce _ -> withObject "HashMap ~Text v" $
1971+ FromJSONKeyCoerce _ -> withObject "HashMap ~Text" $
1624 uc . H.traverseWithKey (\k v -> p v <?> Key k)
1972 uc . H.traverseWithKey (\k v -> p v <?> Key k)
1625− FromJSONKeyText f -> withObject "HashMap k v" $
1973+ FromJSONKeyText f -> withObject "HashMap" $
1626 fmap (mapKey f) . H.traverseWithKey (\k v -> p v <?> Key k)
1974 fmap (mapKey f) . H.traverseWithKey (\k v -> p v <?> Key k)
1627− FromJSONKeyTextParser f -> withObject "HashMap k v" $
1975+ FromJSONKeyTextParser f -> withObject "HashMap" $
1628 H.foldrWithKey (\k v m -> H.insert <$> f k <?> Key k <*> p v <?> Key k <*> m) (pure H.empty)
1976 H.foldrWithKey (\k v m -> H.insert <$> f k <?> Key k <*> p v <?> Key k <*> m) (pure H.empty)
1629− FromJSONKeyValue f -> withArray "Map k v" $ \arr ->
1977+ FromJSONKeyValue f -> withArray "Map" $ \arr ->
1630 fmap H.fromList . Tr.sequence .
1978 fmap H.fromList . Tr.sequence .
1631 zipWith (parseIndexedJSONPair f p) [0..] . V.toList $ arr
1979 zipWith (parseIndexedJSONPair f p) [0..] . V.toList $ arr
1632 where
1980 where
@@ -1649,11 +1997,33 @@ instance FromJSON DotNetTime where
1649 parseJSON = withText "DotNetTime" $ \t ->
1997 parseJSON = withText "DotNetTime" $ \t ->
1650 let (s,m) = T.splitAt (T.length t - 5) t
1998 let (s,m) = T.splitAt (T.length t - 5) t
1651 t' = T.concat [s,".",m]
1999 t' = T.concat [s,".",m]
1652− in case parseTime defaultTimeLocale "/Date(%s%Q)/" (unpack t') of
2000+ in case parseTimeM True defaultTimeLocale "/Date(%s%Q)/" (unpack t') of
1653 Just d -> pure (DotNetTime d)
2001 Just d -> pure (DotNetTime d)
1654 _ -> fail "could not parse .NET time"
2002 _ -> fail "could not parse .NET time"
1655 {-# INLINE parseJSON #-}
2003 {-# INLINE parseJSON #-}
1656
2004
2005+-------------------------------------------------------------------------------
2006+-- primitive
2007+-------------------------------------------------------------------------------
2008+
2009+instance FromJSON a => FromJSON (PM.Array a) where
2010+ -- note: we could do better than this if vector exposed the data
2011+ -- constructor in Data.Vector.
2012+ parseJSON = fmap Exts.fromList . parseJSON
2013+
2014+instance FromJSON a => FromJSON (PM.SmallArray a) where
2015+ parseJSON = fmap Exts.fromList . parseJSON
2016+
2017+#if MIN_VERSION_primitive(0,6,4)
2018+instance (PM.Prim a,FromJSON a) => FromJSON (PM.PrimArray a) where
2019+ parseJSON = fmap Exts.fromList . parseJSON
2020+
2021+#if !MIN_VERSION_primitive(0,7,0)
2022+instance (PM.PrimUnlifted a,FromJSON a) => FromJSON (PM.UnliftedArray a) where
2023+ parseJSON = fmap Exts.fromList . parseJSON
2024+#endif
2025+#endif
2026+
1657-------------------------------------------------------------------------------
2027-------------------------------------------------------------------------------
1658-- time
2028-- time
1659-------------------------------------------------------------------------------
2029-------------------------------------------------------------------------------
@@ -1704,22 +2074,56 @@ instance FromJSONKey UTCTime where
1704 fromJSONKey = FromJSONKeyTextParser (Time.run Time.utcTime)
2074 fromJSONKey = FromJSONKeyTextParser (Time.run Time.utcTime)
1705
2075
1706
2076
1707−-- | /WARNING:/ Only parse lengths of time from trusted input
2077+-- | This instance includes a bounds check to prevent maliciously
1708−-- since an attacker could easily fill up the memory of the target
2078+-- large inputs to fill up the memory of the target system. You can
1709−-- system by specifying a scientific number with a big exponent like
2079+-- newtype 'Scientific' and provide your own instance using
1710−-- @1e1000000000@.
2080+-- 'withScientific' if you want to allow larger inputs.
1711instance FromJSON NominalDiffTime where
2081instance FromJSON NominalDiffTime where
1712− parseJSON = withScientific "NominalDiffTime" $ pure . realToFrac
2082+ parseJSON = withBoundedScientific "NominalDiffTime" $ pure . realToFrac
1713 {-# INLINE parseJSON #-}
2083 {-# INLINE parseJSON #-}
1714
2084
1715
2085
1716−-- | /WARNING:/ Only parse lengths of time from trusted input
2086+-- | This instance includes a bounds check to prevent maliciously
1717−-- since an attacker could easily fill up the memory of the target
2087+-- large inputs to fill up the memory of the target system. You can
1718−-- system by specifying a scientific number with a big exponent like
2088+-- newtype 'Scientific' and provide your own instance using
1719−-- @1e1000000000@.
2089+-- 'withScientific' if you want to allow larger inputs.
1720instance FromJSON DiffTime where
2090instance FromJSON DiffTime where
1721− parseJSON = withScientific "DiffTime" $ pure . realToFrac
2091+ parseJSON = withBoundedScientific "DiffTime" $ pure . realToFrac
1722− {-# INLINE parseJSON #-}
2092+ {-# INLINE parseJSON #-}
2093+
2094+instance FromJSON SystemTime where
2095+ parseJSON v = prependContext "SystemTime" $ do
2096+ n <- parseJSON v
2097+ let n' = floor (n * fromInteger (resolution n) :: Nano)
2098+ let (secs, nano) = n' `divMod` resolution n
2099+ return (MkSystemTime (fromInteger secs) (fromInteger nano))
2100+
2101+instance FromJSON CalendarDiffTime where
2102+ parseJSON = withObject "CalendarDiffTime" $ \obj -> CalendarDiffTime
2103+ <$> obj .: "months"
2104+ <*> obj .: "time"
2105+
2106+instance FromJSON CalendarDiffDays where
2107+ parseJSON = withObject "CalendarDiffDays" $ \obj -> CalendarDiffDays
2108+ <$> obj .: "months"
2109+ <*> obj .: "days"
2110+
2111+instance FromJSON DayOfWeek where
2112+ parseJSON = withText "DaysOfWeek" parseDayOfWeek
2113+
2114+parseDayOfWeek :: T.Text -> Parser DayOfWeek
2115+parseDayOfWeek t = case T.toLower t of
2116+ "monday" -> return Monday
2117+ "tuesday" -> return Tuesday
2118+ "wednesday" -> return Wednesday
2119+ "thursday" -> return Thursday
2120+ "friday" -> return Friday
2121+ "saturday" -> return Saturday
2122+ "sunday" -> return Sunday
2123+ _ -> fail "Invalid week day"
2124+
2125+instance FromJSONKey DayOfWeek where
2126+ fromJSONKey = FromJSONKeyTextParser parseDayOfWeek
1723
2127
1724-------------------------------------------------------------------------------
2128-------------------------------------------------------------------------------
1725-- base Monoid/Semigroup
2129-- base Monoid/Semigroup
@@ -1841,14 +2245,15 @@ instance FromJSON a => FromJSON (Semigroup.Option a) where
1841
2245
1842instance FromJSON1 Proxy where
2246instance FromJSON1 Proxy where
1843 {-# INLINE liftParseJSON #-}
2247 {-# INLINE liftParseJSON #-}
1844− liftParseJSON _ _ Null = pure Proxy
2248+ liftParseJSON _ _ = fromNull "Proxy" Proxy
1845− liftParseJSON _ _ v = typeMismatch "Proxy" v
1846
2249
1847instance FromJSON (Proxy a) where
2250instance FromJSON (Proxy a) where
1848 {-# INLINE parseJSON #-}
2251 {-# INLINE parseJSON #-}
1849− parseJSON Null = pure Proxy
2252+ parseJSON = fromNull "Proxy" Proxy
1850− parseJSON v = typeMismatch "Proxy" v
1851
2253
2254+fromNull :: String -> a -> Value -> Parser a
2255+fromNull _ a Null = pure a
2256+fromNull c _ v = prependContext c (typeMismatch "Null" v)
1852
2257
1853instance FromJSON2 Tagged where
2258instance FromJSON2 Tagged where
1854 liftParseJSON2 _ _ p _ = fmap Tagged . p
2259 liftParseJSON2 _ _ p _ = fmap Tagged . p
@@ -1875,10 +2280,7 @@ instance (FromJSON a, FromJSON b, FromJSON c) => FromJSONKey (a,b,c)
1875instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d) => FromJSONKey (a,b,c,d)
2280instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d) => FromJSONKey (a,b,c,d)
1876
2281
1877instance FromJSONKey Char where
2282instance FromJSONKey Char where
1878− fromJSONKey = FromJSONKeyTextParser $ \t ->
2283+ fromJSONKey = FromJSONKeyTextParser parseChar
1879− if T.length t == 1
1880− then return (T.index t 0)
1881− else typeMismatch "Expected Char but String didn't contain exactly one character" (String t)
1882 fromJSONKeyList = FromJSONKeyText T.unpack
2284 fromJSONKeyList = FromJSONKeyText T.unpack
1883
2285
1884instance (FromJSONKey a, FromJSON a) => FromJSONKey [a] where
2286instance (FromJSONKey a, FromJSON a) => FromJSONKey [a] where
Data/Aeson/Types/Generic.hs+12−8
@@ -1,10 +1,11 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE DeriveFunctor #-}
3{-# LANGUAGE EmptyDataDecls #-}
4{-# LANGUAGE EmptyDataDecls #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FunctionalDependencies #-}
6{-# LANGUAGE FunctionalDependencies #-}
6{-# LANGUAGE KindSignatures #-}
7{-# LANGUAGE KindSignatures #-}
7−{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE NoImplicitPrelude #-}
8{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE TypeOperators #-}
10{-# LANGUAGE TypeOperators #-}
10{-# LANGUAGE UndecidableInstances #-}
11{-# LANGUAGE UndecidableInstances #-}
@@ -25,7 +26,7 @@
25
26
26module Data.Aeson.Types.Generic
27module Data.Aeson.Types.Generic
27 (
28 (
28− IsRecord(..)
29+ IsRecord
29 , AllNullary
30 , AllNullary
30 , Tagged2(..)
31 , Tagged2(..)
31 , True
32 , True
@@ -34,9 +35,9 @@ module Data.Aeson.Types.Generic
34 , Zero
35 , Zero
35 , One
36 , One
36 , ProductSize(..)
37 , ProductSize(..)
38+ , (:*)(..)
37 ) where
39 ) where
38
40
39−import Prelude ()
40import Prelude.Compat
41import Prelude.Compat
41
42
42import GHC.Generics
43import GHC.Generics
@@ -44,12 +45,8 @@ import GHC.Generics
44--------------------------------------------------------------------------------
45--------------------------------------------------------------------------------
45
46
46class IsRecord (f :: * -> *) isRecord | f -> isRecord
47class IsRecord (f :: * -> *) isRecord | f -> isRecord
47− where
48− isUnary :: f a -> Bool
49− isUnary = const True
50
48
51instance (IsRecord f isRecord) => IsRecord (f :*: g) isRecord
49instance (IsRecord f isRecord) => IsRecord (f :*: g) isRecord
52− where isUnary = const False
53#if MIN_VERSION_base(4,9,0)
50#if MIN_VERSION_base(4,9,0)
54instance OVERLAPPING_ IsRecord (M1 S ('MetaSel 'Nothing u ss ds) f) False
51instance OVERLAPPING_ IsRecord (M1 S ('MetaSel 'Nothing u ss ds) f) False
55#else
52#else
@@ -61,7 +58,6 @@ instance IsRecord Par1 True
61instance IsRecord (Rec1 f) True
58instance IsRecord (Rec1 f) True
62instance IsRecord (f :.: g) True
59instance IsRecord (f :.: g) True
63instance IsRecord U1 False
60instance IsRecord U1 False
64− where isUnary = const False
65
61
66--------------------------------------------------------------------------------
62--------------------------------------------------------------------------------
67
63
@@ -80,6 +76,7 @@ instance AllNullary (Rec1 f) False
80instance AllNullary U1 True
76instance AllNullary U1 True
81
77
82newtype Tagged2 (s :: * -> *) b = Tagged2 {unTagged2 :: b}
78newtype Tagged2 (s :: * -> *) b = Tagged2 {unTagged2 :: b}
79+ deriving Functor
83
80
84--------------------------------------------------------------------------------
81--------------------------------------------------------------------------------
85
82
@@ -112,3 +109,10 @@ instance (ProductSize a, ProductSize b) => ProductSize (a :*: b) where
112
109
113instance ProductSize (S1 s a) where
110instance ProductSize (S1 s a) where
114 productSize = Tagged2 1
111 productSize = Tagged2 1
112+
113+--------------------------------------------------------------------------------
114+
115+-- | Simple extensible tuple type to simplify passing around many parameters.
116+data a :* b = a :* b
117+
118+infixr 1 :*
Data/Aeson/Types/Internal.hs+121−10
@@ -2,6 +2,7 @@
2{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+{-# LANGUAGE NoImplicitPrelude #-}
5{-# LANGUAGE Rank2Types #-}
6{-# LANGUAGE Rank2Types #-}
6#if __GLASGOW_HASKELL__ >= 800
7#if __GLASGOW_HASKELL__ >= 800
7-- a) THQ works on cross-compilers and unregisterised GHCs
8-- a) THQ works on cross-compilers and unregisterised GHCs
@@ -44,9 +45,12 @@ module Data.Aeson.Types.Internal
44 , parseEither
45 , parseEither
45 , parseMaybe
46 , parseMaybe
46 , modifyFailure
47 , modifyFailure
48+ , prependFailure
47 , parserThrowError
49 , parserThrowError
48 , parserCatchError
50 , parserCatchError
49 , formatError
51 , formatError
52+ , formatPath
53+ , formatRelativePath
50 , (<?>)
54 , (<?>)
51 -- * Constructors and accessors
55 -- * Constructors and accessors
52 , object
56 , object
@@ -60,11 +64,14 @@ module Data.Aeson.Types.Internal
60 , sumEncoding
64 , sumEncoding
61 , unwrapUnaryRecords
65 , unwrapUnaryRecords
62 , tagSingleConstructors
66 , tagSingleConstructors
67+ , rejectUnknownFields
63 )
68 )
64
69
65 , SumEncoding(..)
70 , SumEncoding(..)
71+ , JSONKeyOptions(keyModifier)
66 , defaultOptions
72 , defaultOptions
67 , defaultTaggedObject
73 , defaultTaggedObject
74+ , defaultJSONKeyOptions
68
75
69 -- * Used for changing CamelCase names into something else.
76 -- * Used for changing CamelCase names into something else.
70 , camelTo
77 , camelTo
@@ -74,7 +81,6 @@ module Data.Aeson.Types.Internal
74 , DotNetTime(..)
81 , DotNetTime(..)
75 ) where
82 ) where
76
83
77−import Prelude ()
78import Prelude.Compat
84import Prelude.Compat
79
85
80import Control.Applicative (Alternative(..))
86import Control.Applicative (Alternative(..))
@@ -96,6 +102,7 @@ import Data.Time.Format (FormatTime)
96import Data.Typeable (Typeable)
102import Data.Typeable (Typeable)
97import Data.Vector (Vector)
103import Data.Vector (Vector)
98import GHC.Generics (Generic)
104import GHC.Generics (Generic)
105+import qualified Control.Monad as Monad
99import qualified Control.Monad.Fail as Fail
106import qualified Control.Monad.Fail as Fail
100import qualified Data.HashMap.Strict as H
107import qualified Data.HashMap.Strict as H
101import qualified Data.Scientific as S
108import qualified Data.Scientific as S
@@ -114,7 +121,7 @@ data JSONPathElement = Key Text
114 | Index {-# UNPACK #-} !Int
121 | Index {-# UNPACK #-} !Int
115 -- ^ JSON path element of an index into an
122 -- ^ JSON path element of an index into an
116 -- array, \"array[index]\".
123 -- array, \"array[index]\".
117− deriving (Eq, Show, Typeable)
124+ deriving (Eq, Show, Typeable, Ord)
118type JSONPath = [JSONPathElement]
125type JSONPath = [JSONPathElement]
119
126
120-- | The internal result of running a 'Parser'.
127-- | The internal result of running a 'Parser'.
@@ -149,7 +156,7 @@ instance Functor Result where
149 fmap _ (Error err) = Error err
156 fmap _ (Error err) = Error err
150 {-# INLINE fmap #-}
157 {-# INLINE fmap #-}
151
158
152−instance Monad IResult where
159+instance Monad.Monad IResult where
153 return = pure
160 return = pure
154 {-# INLINE return #-}
161 {-# INLINE return #-}
155
162
@@ -157,14 +164,16 @@ instance Monad IResult where
157 IError path err >>= _ = IError path err
164 IError path err >>= _ = IError path err
158 {-# INLINE (>>=) #-}
165 {-# INLINE (>>=) #-}
159
166
167+#if !(MIN_VERSION_base(4,13,0))
160 fail = Fail.fail
168 fail = Fail.fail
161 {-# INLINE fail #-}
169 {-# INLINE fail #-}
170+#endif
162
171
163instance Fail.MonadFail IResult where
172instance Fail.MonadFail IResult where
164 fail err = IError [] err
173 fail err = IError [] err
165 {-# INLINE fail #-}
174 {-# INLINE fail #-}
166
175
167−instance Monad Result where
176+instance Monad.Monad Result where
168 return = pure
177 return = pure
169 {-# INLINE return #-}
178 {-# INLINE return #-}
170
179
@@ -172,8 +181,10 @@ instance Monad Result where
172 Error err >>= _ = Error err
181 Error err >>= _ = Error err
173 {-# INLINE (>>=) #-}
182 {-# INLINE (>>=) #-}
174
183
184+#if !(MIN_VERSION_base(4,13,0))
175 fail = Fail.fail
185 fail = Fail.fail
176 {-# INLINE fail #-}
186 {-# INLINE fail #-}
187+#endif
177
188
178instance Fail.MonadFail Result where
189instance Fail.MonadFail Result where
179 fail err = Error err
190 fail err = Error err
@@ -270,7 +281,9 @@ type Failure f r = JSONPath -> String -> f r
270-- | Success continuation.
281-- | Success continuation.
271type Success a f r = a -> f r
282type Success a f r = a -> f r
272
283
273−-- | A JSON parser.
284+-- | A JSON parser. N.B. This might not fit your usual understanding of
285+-- "parser". Instead you might like to think of 'Parser' as a "parse result",
286+-- i.e. a parser to which the input has already been applied.
274newtype Parser a = Parser {
287newtype Parser a = Parser {
275 runParser :: forall f r.
288 runParser :: forall f r.
276 JSONPath
289 JSONPath
@@ -279,14 +292,17 @@ newtype Parser a = Parser {
279 -> f r
292 -> f r
280 }
293 }
281
294
282−instance Monad Parser where
295+instance Monad.Monad Parser where
283 m >>= g = Parser $ \path kf ks -> let ks' a = runParser (g a) path kf ks
296 m >>= g = Parser $ \path kf ks -> let ks' a = runParser (g a) path kf ks
284 in runParser m path kf ks'
297 in runParser m path kf ks'
285 {-# INLINE (>>=) #-}
298 {-# INLINE (>>=) #-}
286 return = pure
299 return = pure
287 {-# INLINE return #-}
300 {-# INLINE return #-}
301+
302+#if !(MIN_VERSION_base(4,13,0))
288 fail = Fail.fail
303 fail = Fail.fail
289 {-# INLINE fail #-}
304 {-# INLINE fail #-}
305+#endif
290
306
291instance Fail.MonadFail Parser where
307instance Fail.MonadFail Parser where
292 fail msg = Parser $ \path kf _ks -> kf (reverse path) msg
308 fail msg = Parser $ \path kf _ks -> kf (reverse path) msg
@@ -329,8 +345,7 @@ instance Monoid (Parser a) where
329apP :: Parser (a -> b) -> Parser a -> Parser b
345apP :: Parser (a -> b) -> Parser a -> Parser b
330apP d e = do
346apP d e = do
331 b <- d
347 b <- d
332− a <- e
348+ b <$> e
333− return (b a)
334{-# INLINE apP #-}
349{-# INLINE apP #-}
335
350
336-- | A JSON \"object\" (key\/value map).
351-- | A JSON \"object\" (key\/value map).
@@ -446,7 +461,17 @@ parseEither m v = runParser (m v) [] onError Right
446-- | Annotate an error message with a
461-- | Annotate an error message with a
447-- <http://goessner.net/articles/JsonPath/ JSONPath> error location.
462-- <http://goessner.net/articles/JsonPath/ JSONPath> error location.
448formatError :: JSONPath -> String -> String
463formatError :: JSONPath -> String -> String
449−formatError path msg = "Error in " ++ format "$" path ++ ": " ++ msg
464+formatError path msg = "Error in " ++ formatPath path ++ ": " ++ msg
465+
466+-- | Format a <http://goessner.net/articles/JsonPath/ JSONPath> as a 'String',
467+-- representing the root object as @$@.
468+formatPath :: JSONPath -> String
469+formatPath path = "$" ++ formatRelativePath path
470+
471+-- | Format a <http://goessner.net/articles/JsonPath/ JSONPath> as a 'String'
472+-- which represents the path relative to some root object.
473+formatRelativePath :: JSONPath -> String
474+formatRelativePath path = format "" path
450 where
475 where
451 format :: String -> JSONPath -> String
476 format :: String -> JSONPath -> String
452 format pfx [] = pfx
477 format pfx [] = pfx
@@ -512,6 +537,15 @@ modifyFailure :: (String -> String) -> Parser a -> Parser a
512modifyFailure f (Parser p) = Parser $ \path kf ks ->
537modifyFailure f (Parser p) = Parser $ \path kf ks ->
513 p path (\p' m -> kf p' (f m)) ks
538 p path (\p' m -> kf p' (f m)) ks
514
539
540+-- | If the inner 'Parser' failed, prepend the given string to the failure
541+-- message.
542+--
543+-- @
544+-- 'prependFailure' s = 'modifyFailure' (s '++')
545+-- @
546+prependFailure :: String -> Parser a -> Parser a
547+prependFailure = modifyFailure . (++)
548+
515-- | Throw a parser error with an additional path.
549-- | Throw a parser error with an additional path.
516--
550--
517-- @since 1.2.1.0
551-- @since 1.2.1.0
@@ -550,6 +584,39 @@ data Options = Options
550 -- ^ If 'True' record fields with a 'Nothing' value will be
584 -- ^ If 'True' record fields with a 'Nothing' value will be
551 -- omitted from the resulting object. If 'False' the resulting
585 -- omitted from the resulting object. If 'False' the resulting
552 -- object will include those fields mapping to @null@.
586 -- object will include those fields mapping to @null@.
587+ --
588+ -- === Note
589+ --
590+ -- Setting 'omitNothingFields' to 'True' only affects fields which are of
591+ -- type 'Maybe' /uniformly/ in the 'ToJSON' or 'FromJSON' instance. In
592+ -- particular, if the type of a field is declared as a type variable, it
593+ -- will not be omitted from the JSON object, unless the field is
594+ -- specialized upfront in the instance.
595+ --
596+ -- ==== __Example__
597+ --
598+ -- The generic instance for the following type @Fruit@ depends on whether
599+ -- the instance head is @Fruit a@ or @Fruit (Maybe a)@.
600+ --
601+ -- @
602+ -- data Fruit a =
603+ -- { apples :: a -- A field whose type is a type variable.
604+ -- , oranges :: 'Maybe' Int
605+ -- }
606+ --
607+ -- options :: 'Options'
608+ -- options = 'defaultOptions' { 'omitNothingFields' = 'True' }
609+ --
610+ -- -- apples required, oranges optional
611+ -- -- Even if 'Data.Aeson.fromJSON' is then specialized to (Fruit ('Maybe' a)).
612+ -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a) where
613+ -- 'Data.Aeson.fromJSON' = 'Data.Aeson.genericFromJSON' options
614+ --
615+ -- -- apples optional, oranges optional
616+ -- -- In this instance, the field apples is uniformly of type ('Maybe' a).
617+ -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit ('Maybe' a)) where
618+ -- 'Data.Aeson.fromJSON' = 'Data.Aeson.genericFromJSON' options
619+ -- @
553 , sumEncoding :: SumEncoding
620 , sumEncoding :: SumEncoding
554 -- ^ Specifies how to encode constructors of a sum datatype.
621 -- ^ Specifies how to encode constructors of a sum datatype.
555 , unwrapUnaryRecords :: Bool
622 , unwrapUnaryRecords :: Bool
@@ -558,10 +625,14 @@ data Options = Options
558 , tagSingleConstructors :: Bool
625 , tagSingleConstructors :: Bool
559 -- ^ Encode types with a single constructor as sums,
626 -- ^ Encode types with a single constructor as sums,
560 -- so that `allNullaryToStringTag` and `sumEncoding` apply.
627 -- so that `allNullaryToStringTag` and `sumEncoding` apply.
628+ , rejectUnknownFields :: Bool
629+ -- ^ Applies only to 'Data.Aeson.FromJSON' instances. If a field appears in
630+ -- the parsed object map, but does not appear in the target object, parsing
631+ -- will fail, with an error message indicating which fields were unknown.
561 }
632 }
562
633
563instance Show Options where
634instance Show Options where
564− show (Options f c a o s u t) =
635+ show (Options f c a o s u t r) =
565 "Options {"
636 "Options {"
566 ++ intercalate ", "
637 ++ intercalate ", "
567 [ "fieldLabelModifier =~ " ++ show (f "exampleField")
638 [ "fieldLabelModifier =~ " ++ show (f "exampleField")
@@ -571,6 +642,7 @@ instance Show Options where
571 , "sumEncoding = " ++ show s
642 , "sumEncoding = " ++ show s
572 , "unwrapUnaryRecords = " ++ show u
643 , "unwrapUnaryRecords = " ++ show u
573 , "tagSingleConstructors = " ++ show t
644 , "tagSingleConstructors = " ++ show t
645+ , "rejectUnknownFields = " ++ show r
574 ]
646 ]
575 ++ "}"
647 ++ "}"
576
648
@@ -614,6 +686,33 @@ data SumEncoding =
614 -- contents of the constructor.
686 -- contents of the constructor.
615 deriving (Eq, Show)
687 deriving (Eq, Show)
616
688
689+-- | Options for encoding keys with 'Data.Aeson.Types.genericFromJSONKey' and
690+-- 'Data.Aeson.Types.genericToJSONKey'.
691+data JSONKeyOptions = JSONKeyOptions
692+ { keyModifier :: String -> String
693+ -- ^ Function applied to keys. Its result is what goes into the encoded
694+ -- 'Value'.
695+ --
696+ -- === __Example__
697+ --
698+ -- The following instances encode the constructor @Bar@ to lower-case keys
699+ -- @\"bar\"@.
700+ --
701+ -- @
702+ -- data Foo = Bar
703+ -- deriving 'Generic'
704+ --
705+ -- opts :: 'JSONKeyOptions'
706+ -- opts = 'defaultJSONKeyOptions' { 'keyModifier' = 'toLower' }
707+ --
708+ -- instance 'ToJSONKey' Foo where
709+ -- 'toJSONKey' = 'genericToJSONKey' opts
710+ --
711+ -- instance 'FromJSONKey' Foo where
712+ -- 'fromJSONKey' = 'genericFromJSONKey' opts
713+ -- @
714+ }
715+
617-- | Default encoding 'Options':
716-- | Default encoding 'Options':
618--
717--
619-- @
718-- @
@@ -625,6 +724,7 @@ data SumEncoding =
625-- , 'sumEncoding' = 'defaultTaggedObject'
724-- , 'sumEncoding' = 'defaultTaggedObject'
626-- , 'unwrapUnaryRecords' = False
725-- , 'unwrapUnaryRecords' = False
627-- , 'tagSingleConstructors' = False
726-- , 'tagSingleConstructors' = False
727+-- , 'rejectUnknownFields' = False
628-- }
728-- }
629-- @
729-- @
630defaultOptions :: Options
730defaultOptions :: Options
@@ -636,6 +736,7 @@ defaultOptions = Options
636 , sumEncoding = defaultTaggedObject
736 , sumEncoding = defaultTaggedObject
637 , unwrapUnaryRecords = False
737 , unwrapUnaryRecords = False
638 , tagSingleConstructors = False
738 , tagSingleConstructors = False
739+ , rejectUnknownFields = False
639 }
740 }
640
741
641-- | Default 'TaggedObject' 'SumEncoding' options:
742-- | Default 'TaggedObject' 'SumEncoding' options:
@@ -652,6 +753,16 @@ defaultTaggedObject = TaggedObject
652 , contentsFieldName = "contents"
753 , contentsFieldName = "contents"
653 }
754 }
654
755
756+-- | Default 'JSONKeyOptions':
757+--
758+-- @
759+-- defaultJSONKeyOptions = 'JSONKeyOptions'
760+-- { 'keyModifier' = 'id'
761+-- }
762+-- @
763+defaultJSONKeyOptions :: JSONKeyOptions
764+defaultJSONKeyOptions = JSONKeyOptions id
765+
655-- | Converts from CamelCase to another lower case, interspersing
766-- | Converts from CamelCase to another lower case, interspersing
656-- the character between all capital letters and their previous
767-- the character between all capital letters and their previous
657-- entries, except those capital letters that appear together,
768-- entries, except those capital letters that appear together,
Data/Aeson/Types/ToJSON.hs+231−81
@@ -5,20 +5,15 @@
5{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE FunctionalDependencies #-}
6{-# LANGUAGE FunctionalDependencies #-}
7{-# LANGUAGE GADTs #-}
7{-# LANGUAGE GADTs #-}
8−{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE NoImplicitPrelude #-}
9−{-# LANGUAGE NamedFieldPuns #-}
10{-# LANGUAGE OverloadedStrings #-}
9{-# LANGUAGE OverloadedStrings #-}
11{-# LANGUAGE PatternGuards #-}
10{-# LANGUAGE PatternGuards #-}
11+{-# LANGUAGE PolyKinds #-}
12{-# LANGUAGE RecordWildCards #-}
12{-# LANGUAGE RecordWildCards #-}
13{-# LANGUAGE ScopedTypeVariables #-}
13{-# LANGUAGE ScopedTypeVariables #-}
14{-# LANGUAGE TypeOperators #-}
14{-# LANGUAGE TypeOperators #-}
15−{-# LANGUAGE TypeSynonymInstances #-}
16{-# LANGUAGE UndecidableInstances #-}
15{-# LANGUAGE UndecidableInstances #-}
17
16
18−#if __GLASGOW_HASKELL__ >= 706
19−{-# LANGUAGE PolyKinds #-}
20−#endif
21−
22#include "overlapping-compat.h"
17#include "overlapping-compat.h"
23#include "incoherent-compat.h"
18#include "incoherent-compat.h"
24
19
@@ -48,15 +43,20 @@ module Data.Aeson.Types.ToJSON
48 , ToJSONKeyFunction(..)
43 , ToJSONKeyFunction(..)
49 , toJSONKeyText
44 , toJSONKeyText
50 , contramapToJSONKeyFunction
45 , contramapToJSONKeyFunction
46+
47+ , GToJSONKey()
48+ , genericToJSONKey
49+
51 -- * Object key-value pairs
50 -- * Object key-value pairs
52 , KeyValue(..)
51 , KeyValue(..)
52+ , KeyValuePair(..)
53+ , FromPairs(..)
53 -- * Functions needed for documentation
54 -- * Functions needed for documentation
54 -- * Encoding functions
55 -- * Encoding functions
55 , listEncoding
56 , listEncoding
56 , listValue
57 , listValue
57 ) where
58 ) where
58
59
59−import Prelude ()
60import Prelude.Compat
60import Prelude.Compat
61
61
62import Control.Applicative (Const(..))
62import Control.Applicative (Const(..))
@@ -64,31 +64,35 @@ import Control.Monad.ST (ST)
64import Data.Aeson.Encoding (Encoding, Encoding', Series, dict, emptyArray_)
64import Data.Aeson.Encoding (Encoding, Encoding', Series, dict, emptyArray_)
65import Data.Aeson.Encoding.Internal ((>*<))
65import Data.Aeson.Encoding.Internal ((>*<))
66import Data.Aeson.Internal.Functions (mapHashKeyVal, mapKeyVal)
66import Data.Aeson.Internal.Functions (mapHashKeyVal, mapKeyVal)
67−import Data.Aeson.Types.Generic (AllNullary, False, IsRecord(..), One, ProductSize, Tagged2(..), True, Zero, productSize)
67+import Data.Aeson.Types.Generic (AllNullary, False, IsRecord, One, ProductSize, Tagged2(..), True, Zero, productSize)
68import Data.Aeson.Types.Internal
68import Data.Aeson.Types.Internal
69import Data.Attoparsec.Number (Number(..))
69import Data.Attoparsec.Number (Number(..))
70import Data.Bits (unsafeShiftR)
70import Data.Bits (unsafeShiftR)
71import Data.DList (DList)
71import Data.DList (DList)
72−import Data.Fixed (Fixed, HasResolution)
72+import Data.Fixed (Fixed, HasResolution, Nano)
73import Data.Foldable (toList)
73import Data.Foldable (toList)
74import Data.Functor.Compose (Compose(..))
74import Data.Functor.Compose (Compose(..))
75+import Data.Functor.Contravariant (Contravariant (..))
75import Data.Functor.Identity (Identity(..))
76import Data.Functor.Identity (Identity(..))
76import Data.Functor.Product (Product(..))
77import Data.Functor.Product (Product(..))
77import Data.Functor.Sum (Sum(..))
78import Data.Functor.Sum (Sum(..))
78import Data.Int (Int16, Int32, Int64, Int8)
79import Data.Int (Int16, Int32, Int64, Int8)
79import Data.List (intersperse)
80import Data.List (intersperse)
80import Data.List.NonEmpty (NonEmpty(..))
81import Data.List.NonEmpty (NonEmpty(..))
81−import Data.Monoid ((<>))
82+import Data.Semigroup ((<>))
82import Data.Proxy (Proxy(..))
83import Data.Proxy (Proxy(..))
83import Data.Ratio (Ratio, denominator, numerator)
84import Data.Ratio (Ratio, denominator, numerator)
84import Data.Scientific (Scientific)
85import Data.Scientific (Scientific)
85import Data.Tagged (Tagged(..))
86import Data.Tagged (Tagged(..))
86import Data.Text (Text, pack)
87import Data.Text (Text, pack)
87import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)
88import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)
89+import Data.Time.Calendar.Compat (CalendarDiffDays (..), DayOfWeek (..))
90+import Data.Time.LocalTime.Compat (CalendarDiffTime (..))
91+import Data.Time.Clock.System.Compat (SystemTime (..))
88−import Data.Time.Format (FormatTime, formatTime)
92+import Data.Time.Format.Compat (FormatTime, formatTime, defaultTimeLocale)
89−import Data.Time.Locale.Compat (defaultTimeLocale)
90import Data.Vector (Vector)
93import Data.Vector (Vector)
91import Data.Version (Version, showVersion)
94import Data.Version (Version, showVersion)
95+import Data.Void (Void, absurd)
92import Data.Word (Word16, Word32, Word64, Word8)
96import Data.Word (Word16, Word32, Word64, Word8)
93import Foreign.Storable (Storable)
97import Foreign.Storable (Storable)
94import Foreign.C.Types (CTime (..))
98import Foreign.C.Types (CTime (..))
@@ -96,7 +100,6 @@ import GHC.Generics
96import Numeric.Natural (Natural)
100import Numeric.Natural (Natural)
97import qualified Data.Aeson.Encoding as E
101import qualified Data.Aeson.Encoding as E
98import qualified Data.Aeson.Encoding.Internal as E (InArray, comma, econcat, retagEncoding)
102import qualified Data.Aeson.Encoding.Internal as E (InArray, comma, econcat, retagEncoding)
99−import qualified Data.ByteString as S
100import qualified Data.ByteString.Lazy as L
103import qualified Data.ByteString.Lazy as L
101import qualified Data.DList as DList
104import qualified Data.DList as DList
102import qualified Data.HashMap.Strict as H
105import qualified Data.HashMap.Strict as H
@@ -125,15 +128,17 @@ import qualified Data.Vector.Unboxed as VU
125import qualified Data.Aeson.Encoding.Builder as EB
128import qualified Data.Aeson.Encoding.Builder as EB
126import qualified Data.ByteString.Builder as B
129import qualified Data.ByteString.Builder as B
127
130
128−#if !(MIN_VERSION_bytestring(0,10,0))
131+import qualified GHC.Exts as Exts
129−import Foreign.ForeignPtr (withForeignPtr)
132+import qualified Data.Primitive.Array as PM
130−import Foreign.Marshal.Utils (copyBytes)
131−import Foreign.Ptr (plusPtr)
132−import qualified Data.ByteString.Internal as S
133+import qualified Data.Primitive.SmallArray as PM
133−import qualified Data.ByteString.Lazy.Internal as L
134+import qualified Data.Primitive.Types as PM
134−#endif
135
135
136−{-# ANN module ("HLint: ignore Reduce duplication"::String) #-}
136+#if MIN_VERSION_primitive(0,6,4)
137+#if !MIN_VERSION_primitive(0,7,0)
138+import qualified Data.Primitive.UnliftedArray as PM
139+#endif
140+import qualified Data.Primitive.PrimArray as PM
141+#endif
137
142
138toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value
143toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value
139toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b)
144toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b)
@@ -262,7 +267,7 @@ genericLiftToEncoding opts te tel = gToJSON opts (To1Args te tel) . from1
262-- @
267-- @
263--
268--
264-- Previous versions of this library only had the 'toJSON' method. Adding
269-- Previous versions of this library only had the 'toJSON' method. Adding
265−-- 'toEncoding' had to reasons:
270+-- 'toEncoding' had two reasons:
266--
271--
267-- 1. toEncoding is more efficient for the common case that the output of
272-- 1. toEncoding is more efficient for the common case that the output of
268-- 'toJSON' is directly serialized to a @ByteString@.
273-- 'toJSON' is directly serialized to a @ByteString@.
@@ -333,6 +338,13 @@ instance KeyValue Pair where
333 name .= value = (name, toJSON value)
338 name .= value = (name, toJSON value)
334 {-# INLINE (.=) #-}
339 {-# INLINE (.=) #-}
335
340
341+-- | Constructs a singleton 'H.HashMap'. For calling functions that
342+-- demand an 'Object' for constructing objects. To be used in
343+-- conjunction with 'mconcat'. Prefer to use 'object' where possible.
344+instance KeyValue Object where
345+ name .= value = H.singleton name (toJSON value)
346+ {-# INLINE (.=) #-}
347+
336-------------------------------------------------------------------------------
348-------------------------------------------------------------------------------
337-- Classes and types for map keys
349-- Classes and types for map keys
338-------------------------------------------------------------------------------
350-------------------------------------------------------------------------------
@@ -390,7 +402,7 @@ instance KeyValue Pair where
390-- newtype wrapper around 'Text'. The recommended approach is to use
402-- newtype wrapper around 'Text'. The recommended approach is to use
391-- generalized newtype deriving:
403-- generalized newtype deriving:
392--
404--
393−-- > newtype RecordId = RecordId { getRecordId :: Text}
405+-- > newtype RecordId = RecordId { getRecordId :: Text }
394-- > deriving (Eq,Ord,ToJSONKey)
406-- > deriving (Eq,Ord,ToJSONKey)
395--
407--
396-- Then we may write:
408-- Then we may write:
@@ -405,8 +417,18 @@ instance KeyValue Pair where
405--
417--
406-- It is possible to get the 'ToJSONKey' instance for free as we did
418-- It is possible to get the 'ToJSONKey' instance for free as we did
407-- with 'Foo'. However, in this case, we have a natural way to go to
419-- with 'Foo'. However, in this case, we have a natural way to go to
408−-- and from 'Text' that does not require any escape sequences. So, in
420+-- and from 'Text' that does not require any escape sequences. So
409−-- this example, 'ToJSONKeyText' will be used instead of 'ToJSONKeyValue'.
421+-- 'ToJSONKeyText' can be used instead of 'ToJSONKeyValue' to encode maps
422+-- as objects instead of arrays of pairs. This instance may be
423+-- implemented using generics as follows:
424+--
425+-- @
426+-- instance 'ToJSONKey' Color where
427+-- 'toJSONKey' = 'genericToJSONKey' 'defaultJSONKeyOptions'
428+-- @
429+--
430+-- === __Low-level implementations__
431+--
410-- The 'Show' instance can be used to help write 'ToJSONKey':
432-- The 'Show' instance can be used to help write 'ToJSONKey':
411--
433--
412-- > instance ToJSONKey Color where
434-- > instance ToJSONKey Color where
@@ -481,16 +503,42 @@ toJSONKeyTextEnc e = ToJSONKeyText tot e
481 -- TODO: dropAround is also used in stringEncoding, which is unfortunate atm
503 -- TODO: dropAround is also used in stringEncoding, which is unfortunate atm
482 tot = T.dropAround (== '"')
504 tot = T.dropAround (== '"')
483 . T.decodeLatin1
505 . T.decodeLatin1
484− . lazyToStrictByteString
506+ . L.toStrict
485 . E.encodingToLazyByteString
507 . E.encodingToLazyByteString
486 . e
508 . e
487
509
510+instance Contravariant ToJSONKeyFunction where
511+ contramap = contramapToJSONKeyFunction
512+
488-- | Contravariant map, as 'ToJSONKeyFunction' is a contravariant functor.
513-- | Contravariant map, as 'ToJSONKeyFunction' is a contravariant functor.
489contramapToJSONKeyFunction :: (b -> a) -> ToJSONKeyFunction a -> ToJSONKeyFunction b
514contramapToJSONKeyFunction :: (b -> a) -> ToJSONKeyFunction a -> ToJSONKeyFunction b
490contramapToJSONKeyFunction h x = case x of
515contramapToJSONKeyFunction h x = case x of
491 ToJSONKeyText f g -> ToJSONKeyText (f . h) (g . h)
516 ToJSONKeyText f g -> ToJSONKeyText (f . h) (g . h)
492 ToJSONKeyValue f g -> ToJSONKeyValue (f . h) (g . h)
517 ToJSONKeyValue f g -> ToJSONKeyValue (f . h) (g . h)
493
518
519+-- 'toJSONKey' for 'Generic' types.
520+-- Deriving is supported for enumeration types, i.e. the sums of nullary
521+-- constructors. The names of constructors will be used as keys for JSON
522+-- objects.
523+--
524+-- See also 'genericFromJSONKey'.
525+--
526+-- === __Example__
527+--
528+-- @
529+-- data Color = Red | Green | Blue
530+-- deriving 'Generic'
531+--
532+-- instance 'ToJSONKey' Color where
533+-- 'toJSONKey' = 'genericToJSONKey' 'defaultJSONKeyOptions'
534+-- @
535+genericToJSONKey :: (Generic a, GToJSONKey (Rep a))
536+ => JSONKeyOptions -> ToJSONKeyFunction a
537+genericToJSONKey opts = toJSONKeyText (pack . keyModifier opts . getConName . from)
538+
539+class GetConName f => GToJSONKey f
540+instance GetConName f => GToJSONKey f
541+
494-------------------------------------------------------------------------------
542-------------------------------------------------------------------------------
495-- Lifings of FromJSON and ToJSON to unary and binary type constructors
543-- Lifings of FromJSON and ToJSON to unary and binary type constructors
496-------------------------------------------------------------------------------
544-------------------------------------------------------------------------------
@@ -655,11 +703,13 @@ instance (ToJSON a) => ToJSON [a] where
655instance OVERLAPPABLE_ (GToJSON enc arity a) => GToJSON enc arity (M1 i c a) where
703instance OVERLAPPABLE_ (GToJSON enc arity a) => GToJSON enc arity (M1 i c a) where
656 -- Meta-information, which is not handled elsewhere, is ignored:
704 -- Meta-information, which is not handled elsewhere, is ignored:
657 gToJSON opts targs = gToJSON opts targs . unM1
705 gToJSON opts targs = gToJSON opts targs . unM1
706+ {-# INLINE gToJSON #-}
658
707
659instance GToJSON enc One Par1 where
708instance GToJSON enc One Par1 where
660 -- Direct occurrences of the last type parameter are encoded with the
709 -- Direct occurrences of the last type parameter are encoded with the
661 -- function passed in as an argument:
710 -- function passed in as an argument:
662 gToJSON _opts (To1Args tj _) = tj . unPar1
711 gToJSON _opts (To1Args tj _) = tj . unPar1
712+ {-# INLINE gToJSON #-}
663
713
664instance ( ConsToJSON enc arity a
714instance ( ConsToJSON enc arity a
665 , AllNullary (C1 c a) allNullary
715 , AllNullary (C1 c a) allNullary
@@ -672,11 +722,13 @@ instance ( ConsToJSON enc arity a
672 . sumToJSON opts targs
722 . sumToJSON opts targs
673 . unM1
723 . unM1
674 | otherwise = consToJSON opts targs . unM1 . unM1
724 | otherwise = consToJSON opts targs . unM1 . unM1
725+ {-# INLINE gToJSON #-}
675
726
676instance (ConsToJSON enc arity a) => GToJSON enc arity (C1 c a) where
727instance (ConsToJSON enc arity a) => GToJSON enc arity (C1 c a) where
677 -- Constructors need to be encoded differently depending on whether they're
728 -- Constructors need to be encoded differently depending on whether they're
678 -- a record or not. This distinction is made by 'consToJSON':
729 -- a record or not. This distinction is made by 'consToJSON':
679 gToJSON opts targs = consToJSON opts targs . unM1
730 gToJSON opts targs = consToJSON opts targs . unM1
731+ {-# INLINE gToJSON #-}
680
732
681instance ( AllNullary (a :+: b) allNullary
733instance ( AllNullary (a :+: b) allNullary
682 , SumToJSON enc arity (a :+: b) allNullary
734 , SumToJSON enc arity (a :+: b) allNullary
@@ -687,6 +739,7 @@ instance ( AllNullary (a :+: b) allNullary
687 -- strings. This distinction is made by 'sumToJSON':
739 -- strings. This distinction is made by 'sumToJSON':
688 gToJSON opts targs = (unTagged :: Tagged allNullary enc -> enc)
740 gToJSON opts targs = (unTagged :: Tagged allNullary enc -> enc)
689 . sumToJSON opts targs
741 . sumToJSON opts targs
742+ {-# INLINE gToJSON #-}
690
743
691--------------------------------------------------------------------------------
744--------------------------------------------------------------------------------
692-- Generic toJSON
745-- Generic toJSON
@@ -695,18 +748,27 @@ instance ( AllNullary (a :+: b) allNullary
695-- possible but makes error messages a bit harder to understand for missing
748-- possible but makes error messages a bit harder to understand for missing
696-- instances.
749-- instances.
697
750
751+instance GToJSON Value arity V1 where
752+ -- Empty values do not exist, which makes the job of formatting them
753+ -- rather easy:
754+ gToJSON _ _ x = x `seq` error "case: V1"
755+ {-# INLINE gToJSON #-}
756+
698instance ToJSON a => GToJSON Value arity (K1 i a) where
757instance ToJSON a => GToJSON Value arity (K1 i a) where
699 -- Constant values are encoded using their ToJSON instance:
758 -- Constant values are encoded using their ToJSON instance:
700 gToJSON _opts _ = toJSON . unK1
759 gToJSON _opts _ = toJSON . unK1
760+ {-# INLINE gToJSON #-}
701
761
702instance ToJSON1 f => GToJSON Value One (Rec1 f) where
762instance ToJSON1 f => GToJSON Value One (Rec1 f) where
703 -- Recursive occurrences of the last type parameter are encoded using their
763 -- Recursive occurrences of the last type parameter are encoded using their
704 -- ToJSON1 instance:
764 -- ToJSON1 instance:
705 gToJSON _opts (To1Args tj tjl) = liftToJSON tj tjl . unRec1
765 gToJSON _opts (To1Args tj tjl) = liftToJSON tj tjl . unRec1
766+ {-# INLINE gToJSON #-}
706
767
707instance GToJSON Value arity U1 where
768instance GToJSON Value arity U1 where
708 -- Empty constructors are encoded to an empty array:
769 -- Empty constructors are encoded to an empty array:
709 gToJSON _opts _ _ = emptyArray
770 gToJSON _opts _ _ = emptyArray
771+ {-# INLINE gToJSON #-}
710
772
711instance ( WriteProduct arity a, WriteProduct arity b
773instance ( WriteProduct arity a, WriteProduct arity b
712 , ProductSize a, ProductSize b
774 , ProductSize a, ProductSize b
@@ -723,6 +785,7 @@ instance ( WriteProduct arity a, WriteProduct arity b
723 where
785 where
724 lenProduct = (unTagged2 :: Tagged2 (a :*: b) Int -> Int)
786 lenProduct = (unTagged2 :: Tagged2 (a :*: b) Int -> Int)
725 productSize
787 productSize
788+ {-# INLINE gToJSON #-}
726
789
727instance ( ToJSON1 f
790instance ( ToJSON1 f
728 , GToJSON Value One g
791 , GToJSON Value One g
@@ -734,6 +797,7 @@ instance ( ToJSON1 f
734 gToJSON opts targs =
797 gToJSON opts targs =
735 let gtj = gToJSON opts targs in
798 let gtj = gToJSON opts targs in
736 liftToJSON gtj (listValue gtj) . unComp1
799 liftToJSON gtj (listValue gtj) . unComp1
800+ {-# INLINE gToJSON #-}
737
801
738--------------------------------------------------------------------------------
802--------------------------------------------------------------------------------
739-- Generic toEncoding
803-- Generic toEncoding
@@ -741,15 +805,18 @@ instance ( ToJSON1 f
741instance ToJSON a => GToJSON Encoding arity (K1 i a) where
805instance ToJSON a => GToJSON Encoding arity (K1 i a) where
742 -- Constant values are encoded using their ToJSON instance:
806 -- Constant values are encoded using their ToJSON instance:
743 gToJSON _opts _ = toEncoding . unK1
807 gToJSON _opts _ = toEncoding . unK1
808+ {-# INLINE gToJSON #-}
744
809
745instance ToJSON1 f => GToJSON Encoding One (Rec1 f) where
810instance ToJSON1 f => GToJSON Encoding One (Rec1 f) where
746 -- Recursive occurrences of the last type parameter are encoded using their
811 -- Recursive occurrences of the last type parameter are encoded using their
747 -- ToEncoding1 instance:
812 -- ToEncoding1 instance:
748 gToJSON _opts (To1Args te tel) = liftToEncoding te tel . unRec1
813 gToJSON _opts (To1Args te tel) = liftToEncoding te tel . unRec1
814+ {-# INLINE gToJSON #-}
749
815
750instance GToJSON Encoding arity U1 where
816instance GToJSON Encoding arity U1 where
751 -- Empty constructors are encoded to an empty array:
817 -- Empty constructors are encoded to an empty array:
752 gToJSON _opts _ _ = E.emptyArray_
818 gToJSON _opts _ _ = E.emptyArray_
819+ {-# INLINE gToJSON #-}
753
820
754instance ( EncodeProduct arity a
821instance ( EncodeProduct arity a
755 , EncodeProduct arity b
822 , EncodeProduct arity b
@@ -759,6 +826,7 @@ instance ( EncodeProduct arity a
759 -- the same size as the product and write the product's elements to it using
826 -- the same size as the product and write the product's elements to it using
760 -- 'encodeProduct':
827 -- 'encodeProduct':
761 gToJSON opts targs p = E.list E.retagEncoding [encodeProduct opts targs p]
828 gToJSON opts targs p = E.list E.retagEncoding [encodeProduct opts targs p]
829+ {-# INLINE gToJSON #-}
762
830
763instance ( ToJSON1 f
831instance ( ToJSON1 f
764 , GToJSON Encoding One g
832 , GToJSON Encoding One g
@@ -770,6 +838,7 @@ instance ( ToJSON1 f
770 gToJSON opts targs =
838 gToJSON opts targs =
771 let gte = gToJSON opts targs in
839 let gte = gToJSON opts targs in
772 liftToEncoding gte (listEncoding gte) . unComp1
840 liftToEncoding gte (listEncoding gte) . unComp1
841+ {-# INLINE gToJSON #-}
773
842
774--------------------------------------------------------------------------------
843--------------------------------------------------------------------------------
775
844
@@ -853,14 +922,14 @@ instance ( IsRecord a isRecord
853 , TaggedObject' enc pairs arity a isRecord
922 , TaggedObject' enc pairs arity a isRecord
854 , FromPairs enc pairs
923 , FromPairs enc pairs
855 , FromString enc
924 , FromString enc
856− , GKeyValue enc pairs
925+ , KeyValuePair enc pairs
857 , Constructor c
926 , Constructor c
858 ) => TaggedObject enc arity (C1 c a)
927 ) => TaggedObject enc arity (C1 c a)
859 where
928 where
860 taggedObject opts targs tagFieldName contentsFieldName =
929 taggedObject opts targs tagFieldName contentsFieldName =
861− fromPairs . (tag <>) . contents
930+ fromPairs . mappend tag . contents
862 where
931 where
863− tag = tagFieldName `gPair`
932+ tag = tagFieldName `pair`
864 (fromString (constructorTagModifier opts (conName (undefined :: t c a p)))
933 (fromString (constructorTagModifier opts (conName (undefined :: t c a p)))
865 :: enc)
934 :: enc)
866 contents =
935 contents =
@@ -872,11 +941,11 @@ class TaggedObject' enc pairs arity f isRecord where
872 -> String -> f a -> Tagged isRecord pairs
941 -> String -> f a -> Tagged isRecord pairs
873
942
874instance ( GToJSON enc arity f
943instance ( GToJSON enc arity f
875− , GKeyValue enc pairs
944+ , KeyValuePair enc pairs
876 ) => TaggedObject' enc pairs arity f False
945 ) => TaggedObject' enc pairs arity f False
877 where
946 where
878 taggedObject' opts targs contentsFieldName =
947 taggedObject' opts targs contentsFieldName =
879− Tagged . (contentsFieldName `gPair`) . gToJSON opts targs
948+ Tagged . (contentsFieldName `pair`) . gToJSON opts targs
880
949
881instance OVERLAPPING_ Monoid pairs => TaggedObject' enc pairs arity U1 False where
950instance OVERLAPPING_ Monoid pairs => TaggedObject' enc pairs arity U1 False where
882 taggedObject' _ _ _ _ = Tagged mempty
951 taggedObject' _ _ _ _ = Tagged mempty
@@ -899,6 +968,9 @@ instance (GetConName a, GetConName b) => GetConName (a :+: b) where
899instance (Constructor c) => GetConName (C1 c a) where
968instance (Constructor c) => GetConName (C1 c a) where
900 getConName = conName
969 getConName = conName
901
970
971+-- For genericToJSONKey
972+instance GetConName a => GetConName (D1 d a) where
973+ getConName (M1 x) = getConName x
902
974
903--------------------------------------------------------------------------------
975--------------------------------------------------------------------------------
904
976
@@ -954,7 +1026,6 @@ class ConsToJSON enc arity f where
954
1026
955class ConsToJSON' enc arity f isRecord where
1027class ConsToJSON' enc arity f isRecord where
956 consToJSON' :: Options -> ToArgs enc arity a
1028 consToJSON' :: Options -> ToArgs enc arity a
957− -> Bool -- ^ Are we a record with one field?
958 -> f a -> Tagged isRecord enc
1029 -> f a -> Tagged isRecord enc
959
1030
960instance ( IsRecord f isRecord
1031instance ( IsRecord f isRecord
@@ -963,23 +1034,29 @@ instance ( IsRecord f isRecord
963 where
1034 where
964 consToJSON opts targs =
1035 consToJSON opts targs =
965 (unTagged :: Tagged isRecord enc -> enc)
1036 (unTagged :: Tagged isRecord enc -> enc)
966− . consToJSON' opts targs (isUnary (undefined :: f a))
1037+ . consToJSON' opts targs
967 {-# INLINE consToJSON #-}
1038 {-# INLINE consToJSON #-}
968
1039
1040+instance OVERLAPPING_
969−instance ( RecordToPairs enc pairs arity f
1041+ ( RecordToPairs enc pairs arity (S1 s f)
970 , FromPairs enc pairs
1042 , FromPairs enc pairs
971 , GToJSON enc arity f
1043 , GToJSON enc arity f
1044+ ) => ConsToJSON' enc arity (S1 s f) True
1045+ where
1046+ consToJSON' opts targs
1047+ | unwrapUnaryRecords opts = Tagged . gToJSON opts targs
1048+ | otherwise = Tagged . fromPairs . recordToPairs opts targs
1049+ {-# INLINE consToJSON' #-}
1050+
1051+instance ( RecordToPairs enc pairs arity f
1052+ , FromPairs enc pairs
972 ) => ConsToJSON' enc arity f True
1053 ) => ConsToJSON' enc arity f True
973 where
1054 where
974− consToJSON' opts targs isUn =
975− Tagged .
976− case (isUn, unwrapUnaryRecords opts) of
977− (True, True) -> gToJSON opts targs
978− _ -> fromPairs . recordToPairs opts targs
1055+ consToJSON' opts targs = Tagged . fromPairs . recordToPairs opts targs
979 {-# INLINE consToJSON' #-}
1056 {-# INLINE consToJSON' #-}
980
1057
981instance GToJSON enc arity f => ConsToJSON' enc arity f False where
1058instance GToJSON enc arity f => ConsToJSON' enc arity f False where
982− consToJSON' opts targs _ = Tagged . gToJSON opts targs
1059+ consToJSON' opts targs = Tagged . gToJSON opts targs
983 {-# INLINE consToJSON' #-}
1060 {-# INLINE consToJSON' #-}
984
1061
985--------------------------------------------------------------------------------
1062--------------------------------------------------------------------------------
@@ -997,7 +1074,7 @@ instance ( Monoid pairs
997 ) => RecordToPairs enc pairs arity (a :*: b)
1074 ) => RecordToPairs enc pairs arity (a :*: b)
998 where
1075 where
999 recordToPairs opts (targs :: ToArgs enc arity p) (a :*: b) =
1076 recordToPairs opts (targs :: ToArgs enc arity p) (a :*: b) =
1000− pairsOf a <> pairsOf b
1077+ pairsOf a `mappend` pairsOf b
1001 where
1078 where
1002 pairsOf :: (RecordToPairs enc pairs arity f) => f p -> pairs
1079 pairsOf :: (RecordToPairs enc pairs arity f) => f p -> pairs
1003 pairsOf = recordToPairs opts targs
1080 pairsOf = recordToPairs opts targs
@@ -1005,7 +1082,7 @@ instance ( Monoid pairs
1005
1082
1006instance ( Selector s
1083instance ( Selector s
1007 , GToJSON enc arity a
1084 , GToJSON enc arity a
1008− , GKeyValue enc pairs
1085+ , KeyValuePair enc pairs
1009 ) => RecordToPairs enc pairs arity (S1 s a)
1086 ) => RecordToPairs enc pairs arity (S1 s a)
1010 where
1087 where
1011 recordToPairs = fieldToPair
1088 recordToPairs = fieldToPair
@@ -1014,7 +1091,7 @@ instance ( Selector s
1014instance INCOHERENT_
1091instance INCOHERENT_
1015 ( Selector s
1092 ( Selector s
1016 , GToJSON enc arity (K1 i (Maybe a))
1093 , GToJSON enc arity (K1 i (Maybe a))
1017− , GKeyValue enc pairs
1094+ , KeyValuePair enc pairs
1018 , Monoid pairs
1095 , Monoid pairs
1019 ) => RecordToPairs enc pairs arity (S1 s (K1 i (Maybe a)))
1096 ) => RecordToPairs enc pairs arity (S1 s (K1 i (Maybe a)))
1020 where
1097 where
@@ -1023,15 +1100,28 @@ instance INCOHERENT_
1023 recordToPairs opts targs m1 = fieldToPair opts targs m1
1100 recordToPairs opts targs m1 = fieldToPair opts targs m1
1024 {-# INLINE recordToPairs #-}
1101 {-# INLINE recordToPairs #-}
1025
1102
1103+instance INCOHERENT_
1104+ ( Selector s
1105+ , GToJSON enc arity (K1 i (Maybe a))
1106+ , KeyValuePair enc pairs
1107+ , Monoid pairs
1108+ ) => RecordToPairs enc pairs arity (S1 s (K1 i (Semigroup.Option a)))
1109+ where
1110+ recordToPairs opts targs = recordToPairs opts targs . unwrap
1111+ where
1112+ unwrap :: S1 s (K1 i (Semigroup.Option a)) p -> S1 s (K1 i (Maybe a)) p
1113+ unwrap (M1 (K1 (Semigroup.Option a))) = M1 (K1 a)
1114+ {-# INLINE recordToPairs #-}
1115+
1026fieldToPair :: (Selector s
1116fieldToPair :: (Selector s
1027 , GToJSON enc arity a
1117 , GToJSON enc arity a
1028− , GKeyValue enc pairs)
1118+ , KeyValuePair enc pairs)
1029 => Options -> ToArgs enc arity p
1119 => Options -> ToArgs enc arity p
1030 -> S1 s a p -> pairs
1120 -> S1 s a p -> pairs
1031fieldToPair opts targs m1 =
1121fieldToPair opts targs m1 =
1032 let key = fieldLabelModifier opts (selName m1)
1122 let key = fieldLabelModifier opts (selName m1)
1033 value = gToJSON opts targs (unM1 m1)
1123 value = gToJSON opts targs (unM1 m1)
1034− in key `gPair` value
1124+ in key `pair` value
1035{-# INLINE fieldToPair #-}
1125{-# INLINE fieldToPair #-}
1036
1126
1037--------------------------------------------------------------------------------
1127--------------------------------------------------------------------------------
@@ -1055,10 +1145,12 @@ instance ( WriteProduct arity a
1055 lenL = len `unsafeShiftR` 1
1145 lenL = len `unsafeShiftR` 1
1056 lenR = len - lenL
1146 lenR = len - lenL
1057 ixR = ix + lenL
1147 ixR = ix + lenL
1148+ {-# INLINE writeProduct #-}
1058
1149
1059instance OVERLAPPABLE_ (GToJSON Value arity a) => WriteProduct arity a where
1150instance OVERLAPPABLE_ (GToJSON Value arity a) => WriteProduct arity a where
1060 writeProduct opts targs mv ix _ =
1151 writeProduct opts targs mv ix _ =
1061 VM.unsafeWrite mv ix . gToJSON opts targs
1152 VM.unsafeWrite mv ix . gToJSON opts targs
1153+ {-# INLINE writeProduct #-}
1062
1154
1063--------------------------------------------------------------------------------
1155--------------------------------------------------------------------------------
1064
1156
@@ -1076,21 +1168,23 @@ instance ( EncodeProduct arity a
1076 encodeProduct opts targs (a :*: b) =
1168 encodeProduct opts targs (a :*: b) =
1077 encodeProduct opts targs a >*<
1169 encodeProduct opts targs a >*<
1078 encodeProduct opts targs b
1170 encodeProduct opts targs b
1171+ {-# INLINE encodeProduct #-}
1079
1172
1080instance OVERLAPPABLE_ (GToJSON Encoding arity a) => EncodeProduct arity a where
1173instance OVERLAPPABLE_ (GToJSON Encoding arity a) => EncodeProduct arity a where
1081 encodeProduct opts targs a = E.retagEncoding $ gToJSON opts targs a
1174 encodeProduct opts targs a = E.retagEncoding $ gToJSON opts targs a
1175+ {-# INLINE encodeProduct #-}
1082
1176
1083--------------------------------------------------------------------------------
1177--------------------------------------------------------------------------------
1084
1178
1085instance ( GToJSON enc arity a
1179instance ( GToJSON enc arity a
1086 , ConsToJSON enc arity a
1180 , ConsToJSON enc arity a
1087 , FromPairs enc pairs
1181 , FromPairs enc pairs
1088− , GKeyValue enc pairs
1182+ , KeyValuePair enc pairs
1089 , Constructor c
1183 , Constructor c
1090 ) => SumToJSON' ObjectWithSingleField enc arity (C1 c a)
1184 ) => SumToJSON' ObjectWithSingleField enc arity (C1 c a)
1091 where
1185 where
1092 sumToJSON' opts targs =
1186 sumToJSON' opts targs =
1093− Tagged . fromPairs . (typ `gPair`) . gToJSON opts targs
1187+ Tagged . fromPairs . (typ `pair`) . gToJSON opts targs
1094 where
1188 where
1095 typ = constructorTagModifier opts $
1189 typ = constructorTagModifier opts $
1096 conName (undefined :: t c a p)
1190 conName (undefined :: t c a p)
@@ -1182,6 +1276,13 @@ instance (ToJSON a, ToJSON b) => ToJSON (Either a b) where
1182 toEncoding = toEncoding2
1276 toEncoding = toEncoding2
1183 {-# INLINE toEncoding #-}
1277 {-# INLINE toEncoding #-}
1184
1278
1279+instance ToJSON Void where
1280+ toJSON = absurd
1281+ {-# INLINE toJSON #-}
1282+
1283+ toEncoding = absurd
1284+ {-# INLINE toEncoding #-}
1285+
1185
1286
1186instance ToJSON Bool where
1287instance ToJSON Bool where
1187 toJSON = Bool
1288 toJSON = Bool
@@ -1865,6 +1966,32 @@ dotNetTime (DotNetTime t) = secs ++ formatMillis t ++ ")/"
1865formatMillis :: (FormatTime t) => t -> String
1966formatMillis :: (FormatTime t) => t -> String
1866formatMillis = take 3 . formatTime defaultTimeLocale "%q"
1967formatMillis = take 3 . formatTime defaultTimeLocale "%q"
1867
1968
1969+-------------------------------------------------------------------------------
1970+-- primitive
1971+-------------------------------------------------------------------------------
1972+
1973+instance ToJSON a => ToJSON (PM.Array a) where
1974+ -- note: we could do better than this if vector exposed the data
1975+ -- constructor in Data.Vector.
1976+ toJSON = toJSON . Exts.toList
1977+ toEncoding = toEncoding . Exts.toList
1978+
1979+instance ToJSON a => ToJSON (PM.SmallArray a) where
1980+ toJSON = toJSON . Exts.toList
1981+ toEncoding = toEncoding . Exts.toList
1982+
1983+#if (MIN_VERSION_primitive(0,6,4))
1984+instance (PM.Prim a,ToJSON a) => ToJSON (PM.PrimArray a) where
1985+ toJSON = toJSON . Exts.toList
1986+ toEncoding = toEncoding . Exts.toList
1987+
1988+#if !MIN_VERSION_primitive(0,7,0)
1989+instance (PM.PrimUnlifted a,ToJSON a) => ToJSON (PM.UnliftedArray a) where
1990+ toJSON = toJSON . Exts.toList
1991+ toEncoding = toEncoding . Exts.toList
1992+#endif
1993+#endif
1994+
1868-------------------------------------------------------------------------------
1995-------------------------------------------------------------------------------
1869-- time
1996-- time
1870-------------------------------------------------------------------------------
1997-------------------------------------------------------------------------------
@@ -1913,7 +2040,7 @@ stringEncoding :: Encoding' Text -> Value
1913stringEncoding = String
2040stringEncoding = String
1914 . T.dropAround (== '"')
2041 . T.dropAround (== '"')
1915 . T.decodeLatin1
2042 . T.decodeLatin1
1916− . lazyToStrictByteString
2043+ . L.toStrict
1917 . E.encodingToLazyByteString
2044 . E.encodingToLazyByteString
1918{-# INLINE stringEncoding #-}
2045{-# INLINE stringEncoding #-}
1919
2046
@@ -1933,6 +2060,50 @@ instance ToJSON DiffTime where
1933 toEncoding = E.scientific . realToFrac
2060 toEncoding = E.scientific . realToFrac
1934 {-# INLINE toEncoding #-}
2061 {-# INLINE toEncoding #-}
1935
2062
2063+-- | Encoded as number
2064+instance ToJSON SystemTime where
2065+ toJSON (MkSystemTime secs nsecs) =
2066+ toJSON (fromIntegral secs + fromIntegral nsecs / 1000000000 :: Nano)
2067+ toEncoding (MkSystemTime secs nsecs) =
2068+ toEncoding (fromIntegral secs + fromIntegral nsecs / 1000000000 :: Nano)
2069+
2070+instance ToJSON CalendarDiffTime where
2071+ toJSON (CalendarDiffTime m nt) = object
2072+ [ "months" .= m
2073+ , "time" .= nt
2074+ ]
2075+ toEncoding (CalendarDiffTime m nt) = E.pairs
2076+ ("months" .= m <> "time" .= nt)
2077+
2078+instance ToJSON CalendarDiffDays where
2079+ toJSON (CalendarDiffDays m d) = object
2080+ [ "months" .= m
2081+ , "days" .= d
2082+ ]
2083+ toEncoding (CalendarDiffDays m d) = E.pairs
2084+ ("months" .= m <> "days" .= d)
2085+
2086+instance ToJSON DayOfWeek where
2087+ toJSON Monday = "monday"
2088+ toJSON Tuesday = "tuesday"
2089+ toJSON Wednesday = "wednesday"
2090+ toJSON Thursday = "thursday"
2091+ toJSON Friday = "friday"
2092+ toJSON Saturday = "saturday"
2093+ toJSON Sunday = "sunday"
2094+
2095+toEncodingDayOfWeek :: DayOfWeek -> E.Encoding' Text
2096+toEncodingDayOfWeek Monday = E.unsafeToEncoding "\"monday\""
2097+toEncodingDayOfWeek Tuesday = E.unsafeToEncoding "\"tuesday\""
2098+toEncodingDayOfWeek Wednesday = E.unsafeToEncoding "\"wednesday\""
2099+toEncodingDayOfWeek Thursday = E.unsafeToEncoding "\"thursday\""
2100+toEncodingDayOfWeek Friday = E.unsafeToEncoding "\"friday\""
2101+toEncodingDayOfWeek Saturday = E.unsafeToEncoding "\"saturday\""
2102+toEncodingDayOfWeek Sunday = E.unsafeToEncoding "\"sunday\""
2103+
2104+instance ToJSONKey DayOfWeek where
2105+ toJSONKey = toJSONKeyTextEnc toEncodingDayOfWeek
2106+
1936-------------------------------------------------------------------------------
2107-------------------------------------------------------------------------------
1937-- base Monoid/Semigroup
2108-- base Monoid/Semigroup
1938-------------------------------------------------------------------------------
2109-------------------------------------------------------------------------------
@@ -2676,47 +2847,26 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g,
2676 toEncoding = toEncoding2
2847 toEncoding = toEncoding2
2677 {-# INLINE toEncoding #-}
2848 {-# INLINE toEncoding #-}
2678
2849
2679−-------------------------------------------------------------------------------
2680−-- pre-bytestring-0.10 compatibility
2681−-------------------------------------------------------------------------------
2682−
2683−{-# INLINE lazyToStrictByteString #-}
2684−lazyToStrictByteString :: L.ByteString -> S.ByteString
2685−#if MIN_VERSION_bytestring(0,10,0)
2686−lazyToStrictByteString = L.toStrict
2687−#else
2688−lazyToStrictByteString = packChunks
2689−
2690−-- packChunks is taken from the blaze-builder package.
2691−
2692−-- | Pack the chunks of a lazy bytestring into a single strict bytestring.
2693−packChunks :: L.ByteString -> S.ByteString
2694−packChunks lbs =
2695− S.unsafeCreate (fromIntegral $ L.length lbs) (copyChunks lbs)
2696− where
2697− copyChunks L.Empty _pf = return ()
2698− copyChunks (L.Chunk (S.PS fpbuf o l) lbs') pf = do
2699− withForeignPtr fpbuf $ \pbuf ->
2700− copyBytes pf (pbuf `plusPtr` o) l
2701− copyChunks lbs' (pf `plusPtr` l)
2702−#endif
2703−
2704--------------------------------------------------------------------------------
2850--------------------------------------------------------------------------------
2705
2851
2852+-- | Wrap a list of pairs as an object.
2706class Monoid pairs => FromPairs enc pairs | enc -> pairs where
2853class Monoid pairs => FromPairs enc pairs | enc -> pairs where
2707 fromPairs :: pairs -> enc
2854 fromPairs :: pairs -> enc
2708
2855
2709−instance FromPairs Encoding Series where
2856+instance (a ~ Value) => FromPairs (Encoding' a) Series where
2710 fromPairs = E.pairs
2857 fromPairs = E.pairs
2711
2858
2712instance FromPairs Value (DList Pair) where
2859instance FromPairs Value (DList Pair) where
2713 fromPairs = object . toList
2860 fromPairs = object . toList
2714
2861
2862+-- | Like 'KeyValue' but the value is already converted to JSON
2863+-- ('Value' or 'Encoding'), and the result actually represents lists of pairs
2864+-- so it can be readily concatenated.
2715−class Monoid kv => GKeyValue v kv where
2865+class Monoid kv => KeyValuePair v kv where
2716− gPair :: String -> v -> kv
2866+ pair :: String -> v -> kv
2717
2867
2718−instance ToJSON v => GKeyValue v (DList Pair) where
2868+instance (v ~ Value) => KeyValuePair v (DList Pair) where
2719− gPair k v = DList.singleton (pack k .= v)
2869+ pair k v = DList.singleton (pack k .= v)
2720
2870
2721−instance GKeyValue Encoding Series where
2871+instance (e ~ Encoding) => KeyValuePair e Series where
2722− gPair = E.pairStr
2872+ pair = E.pairStr
HLint.hsdeleted−10
@@ -1,10 +0,0 @@
1−module HLint.HLint where
2−
3−import "hint" HLint.Default
4−import "hint" HLint.Builtin.All
5−import "hint" HLint.Dollar
6−
7−ignore "Avoid lambda"
8−ignore "Eta reduce"
9−ignore "Functor law"
10−ignore "Use >=>"
Makefile+1−1
@@ -1,2 +1,2 @@
1lint:
1lint:
2− hlint --cpp-include include/ --cpp-file .stack-work/dist/*/*Cabal-*/build/autogen/cabal_macros.h Data/ attoparsec-iso8601/ benchmarks/ examples/ ffi/ pure/ tests/
2+ ./run-hlint.sh --cpp-include include/ --cpp-file travis/cabal_macros.h Data/ attoparsec-iso8601/ benchmarks/ examples/ ffi/ pure/ tests/
README.markdown+1−2
@@ -24,5 +24,4 @@ See what's changed in recent (and upcoming) releases:
24
24
25# Authors
25# Authors
26
26
27−This library is written and maintained by Bryan O'Sullivan,
27+This library was originally written by Bryan O'Sullivan.
28−<bos@serpentine.com>.
aeson.cabal+65−53
@@ -1,5 +1,5 @@
1name: aeson
1name: aeson
2−version: 1.2.1.0
2+version: 1.4.6.0
3license: BSD3
3license: BSD3
4license-file: LICENSE
4license-file: LICENSE
5category: Text, Web, JSON
5category: Text, Web, JSON
@@ -21,20 +21,6 @@ description:
21 To get started, see the documentation for the @Data.Aeson@ module
21 To get started, see the documentation for the @Data.Aeson@ module
22 below.
22 below.
23 .
23 .
24− Parsing performance on a late 2013 MacBook Pro (2.6GHz Core i7),
25− running 64-bit GHC 7.10.1, for mostly-English tweets from Twitter's
26− JSON search API:
27− .
28− * 6.4 KB payloads, English: 7570 msg\/sec (47.6 MB\/sec)
29− .
30− * 14.6 KB payloads, Japanese: 3261 msg\/sec (46.6 MB\/sec)
31− .
32− Encoding performance on the same machine and data:
33− .
34− * 6.4 KB payloads, English: 22738 msg\/sec (142.9 MB\/sec)
35− .
36− * 14.6 KB payloads, Japanese: 15911 msg\/sec (227.4 MB\/sec)
37− .
38 (A note on naming: in Greek mythology, Aeson was the father of Jason.)
24 (A note on naming: in Greek mythology, Aeson was the father of Jason.)
39
25
40extra-source-files:
26extra-source-files:
@@ -56,6 +42,7 @@ extra-source-files:
56 include/*.h
42 include/*.h
57 tests/JSONTestSuite/test_parsing/*.json
43 tests/JSONTestSuite/test_parsing/*.json
58 tests/JSONTestSuite/test_transform/*.json
44 tests/JSONTestSuite/test_transform/*.json
45+ tests/golden/*.expected
59 pure/Data/Aeson/Parser/*.hs
46 pure/Data/Aeson/Parser/*.hs
60
47
61flag developer
48flag developer
@@ -89,6 +76,7 @@ library
89 Data.Aeson.Text
76 Data.Aeson.Text
90 Data.Aeson.Types
77 Data.Aeson.Types
91 Data.Aeson.TH
78 Data.Aeson.TH
79+ Data.Aeson.QQ.Simple
92
80
93 Data.Aeson.Encoding.Internal
81 Data.Aeson.Encoding.Internal
94 Data.Aeson.Internal
82 Data.Aeson.Internal
@@ -112,43 +100,59 @@ library
112 Data.Attoparsec.Time
100 Data.Attoparsec.Time
113 Data.Attoparsec.Time.Internal
101 Data.Attoparsec.Time.Internal
114
102
103+ -- GHC bundled libs
115 build-depends:
104 build-depends:
116− attoparsec >= 0.13.0.1,
105+ base >= 4.7.0.0 && < 5,
117− base >= 4.5 && < 5,
106+ bytestring >= 0.10.4.0 && < 0.11,
118− base-compat >= 0.9.1 && < 0.10,
119− containers >= 0.4.2,
107+ containers >= 0.5.5.1 && < 0.7,
120− deepseq >= 1.3 && < 1.5,
108+ deepseq >= 1.3.0.0 && < 1.5,
121− dlist >= 0.6,
122− ghc-prim >= 0.2,
109+ ghc-prim >= 0.2 && < 0.6,
123− hashable >= 1.1.2.0,
124− scientific >= 0.3.4.7 && < 0.4,
125− tagged >=0.8.3 && <0.9,
126− template-haskell >= 2.7,
110+ template-haskell >= 2.9.0.0 && < 2.16,
127− text >= 1.1.1.0,
111+ text >= 1.2.3.0 && < 1.3,
128− th-abstraction >= 0.2.2 && < 0.3,
129− time >= 1.1.1.4,
112+ time >= 1.4 && < 1.10
130− time-locale-compat >= 0.1.1 && < 0.2,
131− unordered-containers >= 0.2.5.0 && < 0.3,
132− uuid-types >= 1.0.3 && <1.1,
133− vector >= 0.8
134
113
135− if flag(bytestring-builder)
114+ if impl(ghc >= 8.0)
136− build-depends: bytestring >= 0.9.2 && < 0.10.4,
115+ build-depends: bytestring >= 0.10.8.1
137− bytestring-builder >= 0.10.4 && < 1
116+
138− else
117+ -- Compat
118+ build-depends:
119+ base-compat >= 0.9.1 && < 0.12,
120+ time-compat >= 1.9.2.2 && < 1.10
121+
122+ if !impl(ghc >= 8.6)
139− build-depends: bytestring >= 0.10.4 && < 0.11
123+ build-depends:
124+ contravariant >=1.4.1 && <1.6
140
125
141 if !impl(ghc >= 8.0)
126 if !impl(ghc >= 8.0)
142 -- `Data.Semigroup` and `Control.Monad.Fail` and `Control.Monad.IO.Class` are available in base only since GHC 8.0 / base 4.9
127 -- `Data.Semigroup` and `Control.Monad.Fail` and `Control.Monad.IO.Class` are available in base only since GHC 8.0 / base 4.9
143 build-depends:
128 build-depends:
144− semigroups >= 0.18.2 && < 0.19,
129+ semigroups >= 0.18.5 && < 0.20,
145− transformers >= 0.2.2.0,
130+ transformers >= 0.3.0.0 && < 0.6,
146− transformers-compat >= 0.3,
131+ transformers-compat >= 0.6.2 && < 0.7,
147 fail == 4.9.*
132 fail == 4.9.*
148
133
149 if !impl(ghc >= 7.10)
134 if !impl(ghc >= 7.10)
150 -- `Numeric.Natural` is available in base only since GHC 7.10 / base 4.8
135 -- `Numeric.Natural` is available in base only since GHC 7.10 / base 4.8
151− build-depends: nats >= 1 && < 1.2
136+ build-depends: nats >= 1.1.1 && < 1.2,
137+ void >= 0.7.2 && < 0.8
138+
139+ -- cannot use latest version
140+ build-depends:
141+ unordered-containers >= 0.2.8.0 && < 0.3,
142+
143+ -- not in LTS-12.10
144+ tagged >= 0.8.5 && < 0.9,
145+ primitive >= 0.6.3.0 && < 0.8
146+
147+ -- Other dependencies
148+ build-depends:
149+ attoparsec >= 0.13.2.2 && < 0.14,
150+ dlist >= 0.8.0.4 && < 0.9,
151+ hashable >= 1.2.7.0 && < 1.4,
152+ scientific >= 0.3.6.2 && < 0.4,
153+ th-abstraction >= 0.2.8.0 && < 0.4,
154+ uuid-types >= 1.0.3 && < 1.1,
155+ vector >= 0.12.0.1 && < 0.13
152
156
153 ghc-options: -Wall
157 ghc-options: -Wall
154
158
@@ -191,42 +195,49 @@ test-suite tests
191 Functions
195 Functions
192 Instances
196 Instances
193 Options
197 Options
198+ PropUtils
194 Properties
199 Properties
200+ PropertyGeneric
201+ PropertyKeys
202+ PropertyRoundTrip
203+ PropertyRTFunctors
204+ PropertyTH
195 SerializationFormatSpec
205 SerializationFormatSpec
196 Types
206 Types
197 UnitTests
207 UnitTests
198 UnitTests.NullaryConstructors
208 UnitTests.NullaryConstructors
199
209
200 build-depends:
210 build-depends:
201− HUnit,
202− QuickCheck >= 2.8 && <2.11,
211+ QuickCheck >= 2.10.0.1 && < 2.14,
203 aeson,
212 aeson,
204 integer-logarithms >= 1 && <1.1,
213 integer-logarithms >= 1 && <1.1,
205 attoparsec,
214 attoparsec,
206 base,
215 base,
207 base-compat,
216 base-compat,
208− base-orphans >= 0.5.3 && <0.7,
217+ base-orphans >= 0.5.3 && <0.9,
209 base16-bytestring,
218 base16-bytestring,
210 containers,
219 containers,
211 directory,
220 directory,
212 dlist,
221 dlist,
222+ Diff >= 0.4 && < 0.5,
213 filepath,
223 filepath,
214− generic-deriving >= 1.10 && < 1.12,
224+ generic-deriving >= 1.10 && < 1.14,
215 ghc-prim >= 0.2,
225 ghc-prim >= 0.2,
216 hashable >= 1.2.4.0,
226 hashable >= 1.2.4.0,
217 scientific,
227 scientific,
218 tagged,
228 tagged,
219 template-haskell,
229 template-haskell,
220− test-framework,
230+ tasty,
221− test-framework-hunit,
231+ tasty-golden,
222− test-framework-quickcheck2,
232+ tasty-hunit,
233+ tasty-quickcheck,
223 text,
234 text,
224 time,
235 time,
225− time-locale-compat,
236+ time-compat,
226 unordered-containers,
237 unordered-containers,
227 uuid-types,
238 uuid-types,
228 vector,
239 vector,
229− quickcheck-instances >=0.3.14
240+ quickcheck-instances >= 0.3.21 && <0.4
230
241
231 if flag(bytestring-builder)
242 if flag(bytestring-builder)
232 build-depends: bytestring >= 0.9 && < 0.10.4,
243 build-depends: bytestring >= 0.9 && < 0.10.4,
@@ -236,12 +247,13 @@ test-suite tests
236
247
237 if !impl(ghc >= 8.0)
248 if !impl(ghc >= 8.0)
238 build-depends:
249 build-depends:
239− semigroups >= 0.18.2 && < 0.19,
250+ semigroups >= 0.18.2 && < 0.20,
240 transformers >= 0.2.2.0,
251 transformers >= 0.2.2.0,
241 transformers-compat >= 0.3
252 transformers-compat >= 0.3
242
253
243 if !impl(ghc >= 7.10)
254 if !impl(ghc >= 7.10)
244− build-depends: nats >=1 && <1.2
255+ build-depends: nats >=1 && <1.2,
256+ void >=0.7.2 && <0.8
245
257
246 if impl(ghc >= 7.8)
258 if impl(ghc >= 7.8)
247 build-depends: hashable-time >= 0.2 && <0.3
259 build-depends: hashable-time >= 0.2 && <0.3
attoparsec-iso8601/Data/Attoparsec/Time.hs+4−5
@@ -1,6 +1,6 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE ScopedTypeVariables #-}
3−
4-- |
4-- |
5-- Module: Data.Aeson.Parser.Time
5-- Module: Data.Aeson.Parser.Time
6-- Copyright: (c) 2015-2016 Bryan O'Sullivan
6-- Copyright: (c) 2015-2016 Bryan O'Sullivan
@@ -21,7 +21,6 @@ module Data.Attoparsec.Time
21 , zonedTime
21 , zonedTime
22 ) where
22 ) where
23
23
24−import Prelude ()
25import Prelude.Compat
24import Prelude.Compat
26
25
27import Control.Applicative ((<|>))
26import Control.Applicative ((<|>))
@@ -42,9 +41,9 @@ import qualified Data.Time.LocalTime as Local
42day :: Parser Day
41day :: Parser Day
43day = do
42day = do
44 absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id
43 absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id
45− y <- decimal <* char '-'
44+ y <- (decimal <* char '-') <|> fail "date must be of form [+,-]YYYY-MM-DD"
46− m <- twoDigits <* char '-'
45+ m <- (twoDigits <* char '-') <|> fail "date must be of form [+,-]YYYY-MM-DD"
47− d <- twoDigits
46+ d <- twoDigits <|> fail "date must be of form [+,-]YYYY-MM-DD"
48 maybe (fail "invalid date") return (fromGregorianValid (absOrNeg y) m d)
47 maybe (fail "invalid date") return (fromGregorianValid (absOrNeg y) m d)
49
48
50-- | Parse a two-digit integer (e.g. day of month, hour).
49-- | Parse a two-digit integer (e.g. day of month, hour).
attoparsec-iso8601/Data/Attoparsec/Time/Internal.hs+21−18
@@ -1,5 +1,5 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2−
2+{-# LANGUAGE NoImplicitPrelude #-}
3-- |
3-- |
4-- Module: Data.Aeson.Internal.Time
4-- Module: Data.Aeson.Internal.Time
5-- Copyright: (c) 2015-2016 Bryan O'Sullivan
5-- Copyright: (c) 2015-2016 Bryan O'Sullivan
@@ -17,45 +17,48 @@ module Data.Attoparsec.Time.Internal
17 , toTimeOfDay64
17 , toTimeOfDay64
18 ) where
18 ) where
19
19
20−import Prelude ()
21import Prelude.Compat
20import Prelude.Compat
22
21
23import Data.Int (Int64)
22import Data.Int (Int64)
24import Data.Time
23import Data.Time
25import Unsafe.Coerce (unsafeCoerce)
24import Unsafe.Coerce (unsafeCoerce)
26
25
27−#if MIN_VERSION_base(4,7,0)
26+#if MIN_VERSION_time(1,6,0)
28
27
29−import Data.Fixed (Pico, Fixed(MkFixed))
28+import Data.Time.Clock (diffTimeToPicoseconds)
30
29
31−toPico :: Integer -> Pico
30+#endif
32−toPico = MkFixed
33
31
34−fromPico :: Pico -> Integer
32+import Data.Fixed (Pico, Fixed(MkFixed))
35−fromPico (MkFixed i) = i
36
33
37−#else
34+#if !MIN_VERSION_time(1,6,0)
38
35
39−import Data.Fixed (Pico)
36+diffTimeToPicoseconds :: DiffTime -> Integer
37+diffTimeToPicoseconds = unsafeCoerce
38+
39+#endif
40
40
41toPico :: Integer -> Pico
41toPico :: Integer -> Pico
42−toPico = unsafeCoerce
42+toPico = MkFixed
43
43
44fromPico :: Pico -> Integer
44fromPico :: Pico -> Integer
45−fromPico = unsafeCoerce
45+fromPico (MkFixed i) = i
46−
47−#endif
48
46
49-- | Like TimeOfDay, but using a fixed-width integer for seconds.
47-- | Like TimeOfDay, but using a fixed-width integer for seconds.
50data TimeOfDay64 = TOD {-# UNPACK #-} !Int
48data TimeOfDay64 = TOD {-# UNPACK #-} !Int
51 {-# UNPACK #-} !Int
49 {-# UNPACK #-} !Int
52 {-# UNPACK #-} !Int64
50 {-# UNPACK #-} !Int64
53
51
52+posixDayLength :: DiffTime
53+posixDayLength = 86400
54+
54diffTimeOfDay64 :: DiffTime -> TimeOfDay64
55diffTimeOfDay64 :: DiffTime -> TimeOfDay64
56+diffTimeOfDay64 t
57+ | t >= posixDayLength = TOD 23 59 (60000000000000 + pico (t - posixDayLength))
55−diffTimeOfDay64 t = TOD (fromIntegral h) (fromIntegral m) s
58+ | otherwise = TOD (fromIntegral h) (fromIntegral m) s
56− where (h,mp) = fromIntegral pico `quotRem` 3600000000000000
59+ where (h,mp) = pico t `quotRem` 3600000000000000
57− (m,s) = mp `quotRem` 60000000000000
60+ (m,s) = mp `quotRem` 60000000000000
58− pico = unsafeCoerce t :: Integer
61+ pico = fromIntegral . diffTimeToPicoseconds
59
62
60toTimeOfDay64 :: TimeOfDay -> TimeOfDay64
63toTimeOfDay64 :: TimeOfDay -> TimeOfDay64
61toTimeOfDay64 (TimeOfDay h m s) = TOD h m (fromIntegral (fromPico s))
64toTimeOfDay64 (TimeOfDay h m s) = TOD h m (fromIntegral (fromPico s))
attoparsec-iso8601/attoparsec-iso8601.cabal+3−3
@@ -1,5 +1,5 @@
1name: attoparsec-iso8601
1name: attoparsec-iso8601
2−version: 1.0.0.0
2+version: 1.0.1.0
3synopsis: Parsing of ISO 8601 dates, originally from aeson.
3synopsis: Parsing of ISO 8601 dates, originally from aeson.
4description: Parsing of ISO 8601 dates, originally from aeson.
4description: Parsing of ISO 8601 dates, originally from aeson.
5license: BSD3
5license: BSD3
@@ -36,8 +36,8 @@ library
36 Data.Attoparsec.Time
36 Data.Attoparsec.Time
37 build-depends:
37 build-depends:
38 attoparsec >= 0.13.0.1,
38 attoparsec >= 0.13.0.1,
39− base >= 4.5 && < 5,
39+ base >= 4.7 && < 5,
40− base-compat >= 0.9.1 && < 0.10,
40+ base-compat >= 0.9.1 && < 0.12,
41 text >= 1.1.1.0,
41 text >= 1.1.1.0,
42 time >= 1.1.1.4
42 time >= 1.1.1.4
43
43
attoparsec-iso8601/changelog.mdnew file+5
@@ -0,0 +1,5 @@
1+For the latest version of this document, please see [https://github.com/bos/aeson/blob/master/attoparsec-iso8601/changelog.md](https://github.com/bos/aeson/blob/master/attoparsec-iso8601/changelog.md).
2+
3+### 1.0.1.0
4+
5+* Fixes handling of `UTCTime` wrt. leap seconds , thanks to Adam Schønemann
benchmarks/AesonCompareAutoInstances.hsdeleted−282
@@ -1,282 +0,0 @@
1−{-# LANGUAGE DeriveGeneric #-}
2−{-# LANGUAGE FlexibleContexts #-}
3−{-# LANGUAGE TemplateHaskell #-}
4−
5−module Main (main) where
6−
7−import Prelude ()
8−import Prelude.Compat
9−
10−import Control.Monad
11−import Control.DeepSeq (NFData, rnf, deepseq)
12−import Criterion.Main hiding (defaultOptions)
13−import Data.Aeson
14−import Data.Aeson.Encoding
15−import Data.Aeson.TH
16−import Data.Aeson.Types
17−import Data.ByteString.Lazy (ByteString)
18−import Data.Data (Data)
19−import Data.Typeable (Typeable)
20−import GHC.Generics (Generic, Rep)
21−import Options
22−
23−toBS :: Encoding -> ByteString
24−toBS = encodingToLazyByteString
25−
26−gEncode :: (Generic a, GToEncoding Zero (Rep a)) => a -> ByteString
27−gEncode = toBS . genericToEncoding opts
28−
29−--------------------------------------------------------------------------------
30−
31−data D a = Nullary
32− | Unary Int
33− | Product String Char a
34− | Record { testOne :: Double
35− , testTwo :: Bool
36− , testThree :: D a
37− }
38− deriving (Show, Eq)
39−
40−deriveJSON opts ''D
41−
42−instance NFData a => NFData (D a) where
43− rnf Nullary = ()
44− rnf (Unary n) = rnf n
45− rnf (Product s c x) = s `deepseq` c `deepseq` rnf x
46− rnf (Record d b y) = d `deepseq` b `deepseq` rnf y
47−
48−type T = D (D (D ()))
49−
50−d :: T
51−d = Record
52− { testOne = 1234.56789
53− , testTwo = True
54− , testThree = Product "Hello World!" 'a'
55− Record
56− { testOne = 9876.54321
57− , testTwo = False
58− , testThree = Product "Yeehaa!!!" '\n' Nullary
59− }
60− }
61−
62−--------------------------------------------------------------------------------
63−
64−data D' a = Nullary'
65− | Unary' Int
66− | Product' String Char a
67− | Record' { testOne' :: Double
68− , testTwo' :: Bool
69− , testThree' :: D' a
70− }
71− deriving (Show, Eq, Generic)
72−
73−instance ToJSON a => ToJSON (D' a) where
74− toJSON = genericToJSON opts
75−
76−instance FromJSON a => FromJSON (D' a) where
77− parseJSON = genericParseJSON opts
78−
79−instance NFData a => NFData (D' a) where
80− rnf Nullary' = ()
81− rnf (Unary' n) = rnf n
82− rnf (Product' s c x) = s `deepseq` c `deepseq` rnf x
83− rnf (Record' d b y) = d `deepseq` b `deepseq` rnf y
84−
85−type T' = D' (D' (D' ()))
86−
87−d' :: T'
88−d' = Record'
89− { testOne' = 1234.56789
90− , testTwo' = True
91− , testThree' = Product' "Hello World!" 'a'
92− Record'
93− { testOne' = 9876.54321
94− , testTwo' = False
95− , testThree' = Product' "Yeehaa!!!" '\n' Nullary'
96− }
97− }
98−
99−--------------------------------------------------------------------------------
100−
101−data BigRecord = BigRecord
102− { field01 :: !Int, field02 :: !Int, field03 :: !Int, field04 :: !Int, field05 :: !Int
103− , field06 :: !Int, field07 :: !Int, field08 :: !Int, field09 :: !Int, field10 :: !Int
104− , field11 :: !Int, field12 :: !Int, field13 :: !Int, field14 :: !Int, field15 :: !Int
105− , field16 :: !Int, field17 :: !Int, field18 :: !Int, field19 :: !Int, field20 :: !Int
106− , field21 :: !Int, field22 :: !Int, field23 :: !Int, field24 :: !Int, field25 :: !Int
107− } deriving (Show, Eq, Generic)
108−
109−instance NFData BigRecord
110−
111−bigRecord = BigRecord 1 2 3 4 5
112− 6 7 8 9 10
113− 11 12 13 14 15
114− 16 17 18 19 20
115− 21 22 23 24 25
116−
117−return []
118−
119−gBigRecordToJSON :: BigRecord -> Value
120−gBigRecordToJSON = genericToJSON opts
121−
122−gBigRecordEncode :: BigRecord -> ByteString
123−gBigRecordEncode = gEncode
124−
125−gBigRecordFromJSON :: Value -> Result BigRecord
126−gBigRecordFromJSON = parse $ genericParseJSON opts
127−
128−thBigRecordToJSON :: BigRecord -> Value
129−thBigRecordToJSON = $(mkToJSON opts ''BigRecord)
130−
131−thBigRecordEncode :: BigRecord -> ByteString
132−thBigRecordEncode = toBS . $(mkToEncoding opts ''BigRecord)
133−
134−thBigRecordFromJSON :: Value -> Result BigRecord
135−thBigRecordFromJSON = parse $(mkParseJSON opts ''BigRecord)
136−
137−--------------------------------------------------------------------------------
138−
139−data BigProduct = BigProduct
140− !Int !Int !Int !Int !Int
141− !Int !Int !Int !Int !Int
142− !Int !Int !Int !Int !Int
143− !Int !Int !Int !Int !Int
144− !Int !Int !Int !Int !Int
145− deriving (Show, Eq, Generic)
146−
147−instance NFData BigProduct
148−
149−bigProduct = BigProduct 1 2 3 4 5
150− 6 7 8 9 10
151− 11 12 13 14 15
152− 16 17 18 19 20
153− 21 22 23 24 25
154−
155−return []
156−
157−gBigProductToJSON :: BigProduct -> Value
158−gBigProductToJSON = genericToJSON opts
159−
160−gBigProductEncode :: BigProduct -> ByteString
161−gBigProductEncode = gEncode
162−
163−gBigProductFromJSON :: Value -> Result BigProduct
164−gBigProductFromJSON = parse $ genericParseJSON opts
165−
166−thBigProductToJSON :: BigProduct -> Value
167−thBigProductToJSON = $(mkToJSON opts ''BigProduct)
168−
169−thBigProductEncode :: BigProduct -> ByteString
170−thBigProductEncode = toBS . $(mkToEncoding opts ''BigProduct)
171−
172−thBigProductFromJSON :: Value -> Result BigProduct
173−thBigProductFromJSON = parse $(mkParseJSON opts ''BigProduct)
174−
175−--------------------------------------------------------------------------------
176−
177−data BigSum = F01 | F02 | F03 | F04 | F05
178− | F06 | F07 | F08 | F09 | F10
179− | F11 | F12 | F13 | F14 | F15
180− | F16 | F17 | F18 | F19 | F20
181− | F21 | F22 | F23 | F24 | F25
182− deriving (Show, Eq, Generic)
183−
184−instance NFData BigSum
185−
186−bigSum = F25
187−
188−return []
189−
190−gBigSumToJSON :: BigSum -> Value
191−gBigSumToJSON = genericToJSON opts
192−
193−gBigSumEncode :: BigSum -> ByteString
194−gBigSumEncode = gEncode
195−
196−gBigSumFromJSON :: Value -> Result BigSum
197−gBigSumFromJSON = parse $ genericParseJSON opts
198−
199−thBigSumToJSON :: BigSum -> Value
200−thBigSumToJSON = $(mkToJSON opts ''BigSum)
201−
202−thBigSumEncode :: BigSum -> ByteString
203−thBigSumEncode = toBS . $(mkToEncoding opts ''BigSum)
204−
205−thBigSumFromJSON :: Value -> Result BigSum
206−thBigSumFromJSON = parse $(mkParseJSON opts ''BigSum)
207−
208−--------------------------------------------------------------------------------
209−
210−type FJ a = Value -> Result a
211−
212−runBench :: IO ()
213−runBench = defaultMain
214− [ let v = toJSON d
215− in (d, d', v) `deepseq`
216− bgroup "D"
217− [ group "toJSON" (nf toJSON d)
218− (nf toJSON d')
219− , group "encode" (nf encode d)
220− (nf encode d')
221− , group "fromJSON" (nf ( fromJSON :: FJ T ) v)
222− (nf ( fromJSON :: FJ T') v)
223− ]
224− , let v = thBigRecordToJSON bigRecord
225− in bigRecord `deepseq` v `deepseq`
226− bgroup "BigRecord"
227− [ group "toJSON" (nf thBigRecordToJSON bigRecord)
228− (nf gBigRecordToJSON bigRecord)
229− , group "encode" (nf thBigRecordEncode bigRecord)
230− (nf gBigRecordEncode bigRecord)
231− , group "fromJSON" (nf (thBigRecordFromJSON :: FJ BigRecord) v)
232− (nf ( gBigRecordFromJSON :: FJ BigRecord) v)
233− ]
234− , let v = thBigProductToJSON bigProduct
235− in bigProduct `deepseq` v `deepseq`
236− bgroup "BigProduct"
237− [ group "toJSON" (nf thBigProductToJSON bigProduct)
238− (nf gBigProductToJSON bigProduct)
239− , group "encode" (nf thBigProductEncode bigProduct)
240− (nf gBigProductEncode bigProduct)
241− , group "fromJSON" (nf (thBigProductFromJSON :: FJ BigProduct) v)
242− (nf (gBigProductFromJSON :: FJ BigProduct) v)
243− ]
244− , let v = thBigSumToJSON bigSum
245− in bigSum `deepseq` v `deepseq`
246− bgroup "BigSum"
247− [ group "toJSON" (nf thBigSumToJSON bigSum)
248− (nf gBigSumToJSON bigSum)
249− , group "encode" (nf thBigSumEncode bigSum)
250− (nf gBigSumEncode bigSum)
251− , group "fromJSON" (nf (thBigSumFromJSON :: FJ BigSum) v)
252− (nf (gBigSumFromJSON :: FJ BigSum) v)
253− ]
254− ]
255−
256−group n th gen = bgroup n [ bench "th" th
257− , bench "generic" gen
258− ]
259−
260−sanityCheck = do
261− check d toJSON fromJSON encode
262− check d' toJSON fromJSON encode
263− check bigRecord thBigRecordToJSON thBigRecordFromJSON thBigRecordEncode
264− check bigRecord gBigRecordToJSON gBigRecordFromJSON gBigRecordEncode
265− check bigProduct thBigProductToJSON thBigProductFromJSON thBigProductEncode
266− check bigProduct gBigProductToJSON gBigProductFromJSON gBigProductEncode
267− check bigSum thBigSumToJSON thBigSumFromJSON thBigSumEncode
268− check bigSum gBigSumToJSON gBigSumFromJSON gBigSumEncode
269−
270−check :: (Show a, Eq a)
271− => a -> (a -> Value) -> (Value -> Result a) -> (a -> ByteString) -> IO ()
272−check x toJSON fromJSON encode = do
273− unless (Success x == (fromJSON . toJSON) x) $ fail $ "toJSON: " ++ show x
274− unless (Success x == (decode' . encode) x) $ fail $ "encode: " ++ show x
275− where
276− decode' s = case decode s of
277− Just v -> fromJSON v
278− Nothing -> fail ""
279−
280−main = do
281− sanityCheck
282− runBench
benchmarks/AesonEncode.hs+2−2
@@ -1,13 +1,13 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
3
4
4module Main (main) where
5module Main (main) where
5
6
6−import Prelude ()
7import Prelude.Compat
7import Prelude.Compat
8
8
9import Control.DeepSeq
9import Control.DeepSeq
10−import Control.Monad
10+import Control.Monad (forM_)
11import Data.Aeson
11import Data.Aeson
12import Data.Attoparsec.ByteString (IResult(..), parseWith)
12import Data.Attoparsec.ByteString (IResult(..), parseWith)
13import Data.Char (isDigit)
13import Data.Char (isDigit)
benchmarks/AesonFoldable.hs+14−42
@@ -1,16 +1,14 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE PackageImports #-}
3
3
4module Main (main) where
4module Main (main) where
5
5
6import Criterion.Main
6import Criterion.Main
7
7
8−import Prelude ()
9import Prelude.Compat
8import Prelude.Compat
10
9
11import Data.Foldable (toList)
10import Data.Foldable (toList)
12−import qualified "aeson" Data.Aeson as A
11+import qualified Data.Aeson as A
13−import qualified "aeson-benchmarks" Data.Aeson as B
14import qualified Data.Sequence as S
12import qualified Data.Sequence as S
15import qualified Data.Vector as V
13import qualified Data.Vector as V
16import qualified Data.Vector.Unboxed as U
14import qualified Data.Vector.Unboxed as U
@@ -21,10 +19,6 @@ import qualified Data.Vector.Unboxed as U
21
19
22newtype L f = L { getL :: f Int }
20newtype L f = L { getL :: f Int }
23
21
24−instance Foldable f => B.ToJSON (L f) where
25− toJSON = error "do not use this"
26− toEncoding = B.toEncoding . toList . getL
27−
28instance Foldable f => A.ToJSON (L f) where
22instance Foldable f => A.ToJSON (L f) where
29 toJSON = error "do not use this"
23 toJSON = error "do not use this"
30 toEncoding = A.toEncoding . toList . getL
24 toEncoding = A.toEncoding . toList . getL
@@ -35,10 +29,6 @@ instance Foldable f => A.ToJSON (L f) where
35
29
36newtype F f = F { getF :: f Int }
30newtype F f = F { getF :: f Int }
37
31
38−instance Foldable f => B.ToJSON (F f) where
39− toJSON = error "do not use this"
40− toEncoding = B.foldable . getF
41−
42instance Foldable f => A.ToJSON (F f) where
32instance Foldable f => A.ToJSON (F f) where
43 toJSON = error "do not use this"
33 toJSON = error "do not use this"
44 toEncoding = A.foldable . getF
34 toEncoding = A.foldable . getF
@@ -63,52 +53,34 @@ valueUVector = U.fromList valueList
63-- Main
53-- Main
64-------------------------------------------------------------------------------
54-------------------------------------------------------------------------------
65
55
66−benchEncodeA
56+benchEncode
67 :: A.ToJSON a
57 :: A.ToJSON a
68 => String
58 => String
69 -> a
59 -> a
70 -> Benchmark
60 -> Benchmark
71−benchEncodeA name val
61+benchEncode name val
72 = bench ("A " ++ name) $ nf A.encode val
62 = bench ("A " ++ name) $ nf A.encode val
73
63
74−benchEncodeB
75− :: B.ToJSON a
76− => String
77− -> a
78− -> Benchmark
79−benchEncodeB name val
80− = bench ("B " ++ name) $ nf B.encode val
81−
82main :: IO ()
64main :: IO ()
83main = defaultMain
65main = defaultMain
84 [ bgroup "encode"
66 [ bgroup "encode"
85 [ bgroup "List"
67 [ bgroup "List"
86− [ benchEncodeB "-" valueList
87− , benchEncodeB "L" $ L valueList
88− , benchEncodeB "F" $ F valueList
89− , benchEncodeA "-" valueList
68+ [ benchEncode "-" valueList
90− , benchEncodeA "L" $ L valueList
69+ , benchEncode "L" $ L valueList
91− , benchEncodeA "F" $ F valueList
70+ , benchEncode "F" $ F valueList
92 ]
71 ]
93 , bgroup "Seq"
72 , bgroup "Seq"
94− [ benchEncodeB "-" valueSeq
95− , benchEncodeB "L" $ L valueSeq
96− , benchEncodeB "F" $ F valueSeq
97− , benchEncodeA "-" valueSeq
73+ [ benchEncode "-" valueSeq
98− , benchEncodeA "L" $ L valueSeq
74+ , benchEncode "L" $ L valueSeq
99− , benchEncodeA "F" $ F valueSeq
75+ , benchEncode "F" $ F valueSeq
100 ]
76 ]
101 , bgroup "Vector"
77 , bgroup "Vector"
102− [ benchEncodeB "-" valueVector
103− , benchEncodeB "L" $ L valueVector
104− , benchEncodeB "F" $ F valueVector
105− , benchEncodeA "-" valueVector
78+ [ benchEncode "-" valueVector
106− , benchEncodeA "L" $ L valueVector
79+ , benchEncode "L" $ L valueVector
107− , benchEncodeA "F" $ F valueVector
80+ , benchEncode "F" $ F valueVector
108 ]
81 ]
109 , bgroup "Vector.Unboxed"
82 , bgroup "Vector.Unboxed"
110− [ benchEncodeB "-" valueUVector
111− , benchEncodeA "-" valueUVector
83+ [ benchEncode "-" valueUVector
112 ]
84 ]
113 ]
85 ]
114 ]
86 ]
benchmarks/AesonMap.hs+39−56
@@ -1,13 +1,11 @@
1{-# LANGUAGE FlexibleContexts #-}
1{-# LANGUAGE FlexibleContexts #-}
2−{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
3−{-# LANGUAGE PackageImports #-}
4{-# LANGUAGE RankNTypes #-}
3{-# LANGUAGE RankNTypes #-}
5
4
6{-# OPTIONS_GHC -fno-warn-orphans #-}
5{-# OPTIONS_GHC -fno-warn-orphans #-}
7
6
8module Main (main) where
7module Main (main) where
9
8
10−import Prelude ()
11import Prelude.Compat
9import Prelude.Compat
12
10
13import Control.DeepSeq
11import Control.DeepSeq
@@ -15,9 +13,8 @@ import Criterion.Main
15import Data.Hashable
13import Data.Hashable
16import Data.Proxy (Proxy (..))
14import Data.Proxy (Proxy (..))
17import Data.Tagged (Tagged (..))
15import Data.Tagged (Tagged (..))
18−import qualified "aeson" Data.Aeson as A
16+import Data.Aeson
19−import qualified "aeson-benchmarks" Data.Aeson as B
20−import qualified "aeson-benchmarks" Data.Aeson.Types as B (fromJSONKeyCoerce)
17+import Data.Aeson.Types (fromJSONKeyCoerce)
21import qualified Data.ByteString.Lazy as LBS
18import qualified Data.ByteString.Lazy as LBS
22import qualified Data.HashMap.Strict as HM
19import qualified Data.HashMap.Strict as HM
23import qualified Data.Map as M
20import qualified Data.Map as M
@@ -47,10 +44,10 @@ instance NFData T1 where
47instance Hashable T1 where
44instance Hashable T1 where
48 hashWithSalt salt (T1 t) = hashWithSalt salt t
45 hashWithSalt salt (T1 t) = hashWithSalt salt t
49
46
50−instance B.FromJSON T1 where
47+instance FromJSON T1 where
51− parseJSON = B.withText "T1" $ pure . T1
48+ parseJSON = withText "T1" $ pure . T1
52−instance B.FromJSONKey T1 where
49+instance FromJSONKey T1 where
53− fromJSONKey = B.FromJSONKeyText T1
50+ fromJSONKey = FromJSONKeyText T1
54
51
55-------------------------------------------------------------------------------
52-------------------------------------------------------------------------------
56-- Coerce
53-- Coerce
@@ -64,10 +61,10 @@ instance NFData T2 where
64instance Hashable T2 where
61instance Hashable T2 where
65 hashWithSalt salt (T2 t) = hashWithSalt salt t
62 hashWithSalt salt (T2 t) = hashWithSalt salt t
66
63
67−instance B.FromJSON T2 where
64+instance FromJSON T2 where
68− parseJSON = B.withText "T2" $ pure . T2
65+ parseJSON = withText "T2" $ pure . T2
69−instance B.FromJSONKey T2 where
66+instance FromJSONKey T2 where
70− fromJSONKey = B.fromJSONKeyCoerce
67+ fromJSONKey = fromJSONKeyCoerce
71
68
72-------------------------------------------------------------------------------
69-------------------------------------------------------------------------------
73-- TextParser
70-- TextParser
@@ -81,10 +78,10 @@ instance NFData T3 where
81instance Hashable T3 where
78instance Hashable T3 where
82 hashWithSalt salt (T3 t) = hashWithSalt salt t
79 hashWithSalt salt (T3 t) = hashWithSalt salt t
83
80
84−instance B.FromJSON T3 where
81+instance FromJSON T3 where
85− parseJSON = B.withText "T3" $ pure . T3
82+ parseJSON = withText "T3" $ pure . T3
86−instance B.FromJSONKey T3 where
83+instance FromJSONKey T3 where
87− fromJSONKey = B.FromJSONKeyTextParser (pure . T3)
84+ fromJSONKey = FromJSONKeyTextParser (pure . T3)
88
85
89-------------------------------------------------------------------------------
86-------------------------------------------------------------------------------
90-- Values
87-- Values
@@ -97,40 +94,30 @@ value1000 = value 1000
97value10000 = value 10000
94value10000 = value 10000
98
95
99encodedValue10 :: LBS.ByteString
96encodedValue10 :: LBS.ByteString
100−encodedValue10 = B.encode value10
97+encodedValue10 = encode value10
101
98
102encodedValue100 :: LBS.ByteString
99encodedValue100 :: LBS.ByteString
103−encodedValue100 = B.encode value100
100+encodedValue100 = encode value100
104
101
105encodedValue1000 :: LBS.ByteString
102encodedValue1000 :: LBS.ByteString
106−encodedValue1000 = B.encode value1000
103+encodedValue1000 = encode value1000
107
104
108encodedValue10000 :: LBS.ByteString
105encodedValue10000 :: LBS.ByteString
109−encodedValue10000 = B.encode value10000
106+encodedValue10000 = encode value10000
110
107
111-------------------------------------------------------------------------------
108-------------------------------------------------------------------------------
112-- Helpers
109-- Helpers
113-------------------------------------------------------------------------------
110-------------------------------------------------------------------------------
114
111
115−decodeHMB
112+decodeHM
116− :: (B.FromJSONKey k, Eq k, Hashable k)
113+ :: (FromJSON (HM.HashMap k T.Text), Eq k, Hashable k)
117 => Proxy k -> LBS.ByteString -> Maybe (HM.HashMap k T.Text)
114 => Proxy k -> LBS.ByteString -> Maybe (HM.HashMap k T.Text)
118−decodeHMB _ = B.decode
115+decodeHM _ = decode
119
116
120−decodeHMA
117+decodeMap
121− :: (A.FromJSON (HM.HashMap k T.Text), Eq k, Hashable k)
122− => Proxy k -> LBS.ByteString -> Maybe (HM.HashMap k T.Text)
123−decodeHMA _ = A.decode
124−
125−decodeMapB
126− :: (B.FromJSONKey k, Ord k)
127− => Proxy k -> LBS.ByteString -> Maybe (M.Map k T.Text)
128−decodeMapB _ = B.decode
129−
130−decodeMapA
131− :: (A.FromJSON (M.Map k T.Text), Ord k)
118+ :: (FromJSON (M.Map k T.Text), Ord k)
132 => Proxy k -> LBS.ByteString -> Maybe (M.Map k T.Text)
119 => Proxy k -> LBS.ByteString -> Maybe (M.Map k T.Text)
133−decodeMapA _ = A.decode
120+decodeMap _ = decode
134
121
135proxyText :: Proxy T.Text
122proxyText :: Proxy T.Text
136proxyText = Proxy
123proxyText = Proxy
@@ -156,15 +143,14 @@ benchDecodeHM
156 -> LBS.ByteString
143 -> LBS.ByteString
157 -> Benchmark
144 -> Benchmark
158benchDecodeHM name val = bgroup name
145benchDecodeHM name val = bgroup name
159− [ bench "Text" $ nf (decodeHMB proxyText) val
146+ [ bench "Text" $ nf (decodeHM proxyText) val
160− , bench "Identity" $ nf (decodeHMB proxyT1) val
147+ , bench "Identity" $ nf (decodeHM proxyT1) val
161− , bench "Coerce" $ nf (decodeHMB proxyT2) val
148+ , bench "Coerce" $ nf (decodeHM proxyT2) val
162− , bench "Parser" $ nf (decodeHMB proxyT3) val
149+ , bench "Parser" $ nf (decodeHM proxyT3) val
163− , bench "aeson-hackage" $ nf (decodeHMA proxyText) val
164− , bench "Tagged Text" $ nf (decodeHMB $ proxyTagged proxyText) val
150+ , bench "Tagged Text" $ nf (decodeHM $ proxyTagged proxyText) val
165− , bench "Tagged Identity" $ nf (decodeHMB $ proxyTagged proxyT1) val
151+ , bench "Tagged Identity" $ nf (decodeHM $ proxyTagged proxyT1) val
166− , bench "Tagged Coerce" $ nf (decodeHMB $ proxyTagged proxyT2) val
152+ , bench "Tagged Coerce" $ nf (decodeHM $ proxyTagged proxyT2) val
167− , bench "Tagged Parser" $ nf (decodeHMB $ proxyTagged proxyT3) val
153+ , bench "Tagged Parser" $ nf (decodeHM $ proxyTagged proxyT3) val
168 ]
154 ]
169
155
170benchDecodeMap
156benchDecodeMap
@@ -172,11 +158,10 @@ benchDecodeMap
172 -> LBS.ByteString
158 -> LBS.ByteString
173 -> Benchmark
159 -> Benchmark
174benchDecodeMap name val = bgroup name
160benchDecodeMap name val = bgroup name
175− [ bench "Text" $ nf (decodeMapB proxyText) val
161+ [ bench "Text" $ nf (decodeMap proxyText) val
176− , bench "Identity" $ nf (decodeMapB proxyT1) val
162+ , bench "Identity" $ nf (decodeMap proxyT1) val
177− , bench "Coerce" $ nf (decodeMapB proxyT2) val
163+ , bench "Coerce" $ nf (decodeMap proxyT2) val
178− , bench "Parser" $ nf (decodeMapB proxyT3) val
164+ , bench "Parser" $ nf (decodeMap proxyT3) val
179− , bench "aeson-hackage" $ nf (decodeMapA proxyText) val
180 ]
165 ]
181
166
182benchEncodeHM
167benchEncodeHM
@@ -184,8 +169,7 @@ benchEncodeHM
184 -> HM.HashMap T.Text T.Text
169 -> HM.HashMap T.Text T.Text
185 -> Benchmark
170 -> Benchmark
186benchEncodeHM name val = bgroup name
171benchEncodeHM name val = bgroup name
187− [ bench "Text" $ nf B.encode val
172+ [ bench "Text" $ nf encode val
188− , bench "aeson-0.11" $ nf A.encode val
189 ]
173 ]
190
174
191benchEncodeMap
175benchEncodeMap
@@ -193,8 +177,7 @@ benchEncodeMap
193 -> HM.HashMap T.Text T.Text
177 -> HM.HashMap T.Text T.Text
194 -> Benchmark
178 -> Benchmark
195benchEncodeMap name val = bgroup name
179benchEncodeMap name val = bgroup name
196− [ bench "Text" $ nf B.encode val'
180+ [ bench "Text" $ nf encode val'
197− , bench "aeson-0.11" $ nf A.encode val'
198 ]
181 ]
199 where
182 where
200 val' :: M.Map T.Text T.Text
183 val' :: M.Map T.Text T.Text
benchmarks/AesonParse.hs+2−3
@@ -1,13 +1,12 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
3−{-# LANGUAGE PackageImports #-}
4
4
5module Main (main) where
5module Main (main) where
6
6
7−import Prelude ()
8import Prelude.Compat
7import Prelude.Compat
9
8
10−import "aeson-benchmarks" Data.Aeson
9+import Data.Aeson
11import Control.Monad
10import Control.Monad
12import Data.Attoparsec.ByteString (IResult(..), parseWith)
11import Data.Attoparsec.ByteString (IResult(..), parseWith)
13import Data.Time.Clock
12import Data.Time.Clock
benchmarks/AesonTuples.hs+2−1
@@ -1,6 +1,7 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
1module Main (main) where
3module Main (main) where
2
4
3−import Prelude ()
4import Prelude.Compat
5import Prelude.Compat
5
6
6import Control.DeepSeq (deepseq)
7import Control.DeepSeq (deepseq)
benchmarks/Auto/G/BigProduct.hsnew file+33
@@ -0,0 +1,33 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+
3+module Auto.G.BigProduct where
4+
5+import Control.DeepSeq
6+import Data.Aeson
7+import GHC.Generics (Generic)
8+import Options
9+
10+data BigProduct = BigProduct
11+ !Int !Int !Int !Int !Int
12+ !Int !Int !Int !Int !Int
13+ !Int !Int !Int !Int !Int
14+ !Int !Int !Int !Int !Int
15+ !Int !Int !Int !Int !Int
16+ deriving (Show, Eq, Generic)
17+
18+instance NFData BigProduct where
19+ rnf a = a `seq` ()
20+
21+instance ToJSON BigProduct where
22+ toJSON = genericToJSON opts
23+ toEncoding = genericToEncoding opts
24+
25+instance FromJSON BigProduct where
26+ parseJSON = genericParseJSON opts
27+
28+bigProduct :: BigProduct
29+bigProduct = BigProduct 1 2 3 4 5
30+ 6 7 8 9 10
31+ 11 12 13 14 15
32+ 16 17 18 19 20
33+ 21 22 23 24 25
benchmarks/Auto/G/BigRecord.hsnew file+33
@@ -0,0 +1,33 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+
3+module Auto.G.BigRecord where
4+
5+import Control.DeepSeq
6+import Data.Aeson
7+import GHC.Generics (Generic)
8+import Options
9+
10+data BigRecord = BigRecord
11+ { field01 :: !Int, field02 :: !Int, field03 :: !Int, field04 :: !Int, field05 :: !Int
12+ , field06 :: !Int, field07 :: !Int, field08 :: !Int, field09 :: !Int, field10 :: !Int
13+ , field11 :: !Int, field12 :: !Int, field13 :: !Int, field14 :: !Int, field15 :: !Int
14+ , field16 :: !Int, field17 :: !Int, field18 :: !Int, field19 :: !Int, field20 :: !Int
15+ , field21 :: !Int, field22 :: !Int, field23 :: !Int, field24 :: !Int, field25 :: !Int
16+ } deriving (Show, Eq, Generic)
17+
18+instance NFData BigRecord where
19+ rnf a = a `seq` ()
20+
21+instance ToJSON BigRecord where
22+ toJSON = genericToJSON opts
23+ toEncoding = genericToEncoding opts
24+
25+instance FromJSON BigRecord where
26+ parseJSON = genericParseJSON opts
27+
28+bigRecord :: BigRecord
29+bigRecord = BigRecord 1 2 3 4 5
30+ 6 7 8 9 10
31+ 11 12 13 14 15
32+ 16 17 18 19 20
33+ 21 22 23 24 25
benchmarks/Auto/G/BigSum.hsnew file+28
@@ -0,0 +1,28 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+
3+module Auto.G.BigSum where
4+
5+import Control.DeepSeq
6+import Data.Aeson
7+import GHC.Generics (Generic)
8+import Options
9+
10+data BigSum = F01 | F02 | F03 | F04 | F05
11+ | F06 | F07 | F08 | F09 | F10
12+ | F11 | F12 | F13 | F14 | F15
13+ | F16 | F17 | F18 | F19 | F20
14+ | F21 | F22 | F23 | F24 | F25
15+ deriving (Show, Eq, Generic)
16+
17+instance NFData BigSum where
18+ rnf a = a `seq` ()
19+
20+instance ToJSON BigSum where
21+ toJSON = genericToJSON opts
22+ toEncoding = genericToEncoding opts
23+
24+instance FromJSON BigSum where
25+ parseJSON = genericParseJSON opts
26+
27+bigSum :: BigSum
28+bigSum = F25
benchmarks/Auto/G/D.hsnew file+44
@@ -0,0 +1,44 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+
3+module Auto.G.D where
4+
5+import Control.DeepSeq
6+import Data.Aeson
7+import GHC.Generics (Generic)
8+import Options
9+
10+data D a = Nullary
11+ | Unary Int
12+ | Product String Char a
13+ | Record { testOne :: Double
14+ , testTwo :: Bool
15+ , testThree :: D a
16+ }
17+ deriving (Show, Eq, Generic)
18+
19+instance NFData a => NFData (D a) where
20+ rnf Nullary = ()
21+ rnf (Unary n) = rnf n
22+ rnf (Product s c x) = s `deepseq` c `deepseq` rnf x
23+ rnf (Record a b y) = a `deepseq` b `deepseq` rnf y
24+
25+instance ToJSON a => ToJSON (D a) where
26+ toJSON = genericToJSON opts
27+ toEncoding = genericToEncoding opts
28+
29+instance FromJSON a => FromJSON (D a) where
30+ parseJSON = genericParseJSON opts
31+
32+type T = D (D (D ()))
33+
34+d :: T
35+d = Record
36+ { testOne = 1234.56789
37+ , testTwo = True
38+ , testThree = Product "Hello World!" 'a'
39+ Record
40+ { testOne = 9876.54321
41+ , testTwo = False
42+ , testThree = Product "Yeehaa!!!" '\n' Nullary
43+ }
44+ }
benchmarks/Auto/T/BigProduct.hsnew file+27
@@ -0,0 +1,27 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+
3+module Auto.T.BigProduct where
4+
5+import Control.DeepSeq
6+import Data.Aeson.TH
7+import Options
8+
9+data BigProduct = BigProduct
10+ !Int !Int !Int !Int !Int
11+ !Int !Int !Int !Int !Int
12+ !Int !Int !Int !Int !Int
13+ !Int !Int !Int !Int !Int
14+ !Int !Int !Int !Int !Int
15+ deriving (Show, Eq)
16+
17+instance NFData BigProduct where
18+ rnf a = a `seq` ()
19+
20+deriveJSON opts ''BigProduct
21+
22+bigProduct :: BigProduct
23+bigProduct = BigProduct 1 2 3 4 5
24+ 6 7 8 9 10
25+ 11 12 13 14 15
26+ 16 17 18 19 20
27+ 21 22 23 24 25
benchmarks/Auto/T/BigRecord.hsnew file+27
@@ -0,0 +1,27 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+
3+module Auto.T.BigRecord where
4+
5+import Control.DeepSeq
6+import Data.Aeson.TH
7+import Options
8+
9+data BigRecord = BigRecord
10+ { field01 :: !Int, field02 :: !Int, field03 :: !Int, field04 :: !Int, field05 :: !Int
11+ , field06 :: !Int, field07 :: !Int, field08 :: !Int, field09 :: !Int, field10 :: !Int
12+ , field11 :: !Int, field12 :: !Int, field13 :: !Int, field14 :: !Int, field15 :: !Int
13+ , field16 :: !Int, field17 :: !Int, field18 :: !Int, field19 :: !Int, field20 :: !Int
14+ , field21 :: !Int, field22 :: !Int, field23 :: !Int, field24 :: !Int, field25 :: !Int
15+ } deriving (Show, Eq)
16+
17+instance NFData BigRecord where
18+ rnf a = a `seq` ()
19+
20+deriveJSON opts ''BigRecord
21+
22+bigRecord :: BigRecord
23+bigRecord = BigRecord 1 2 3 4 5
24+ 6 7 8 9 10
25+ 11 12 13 14 15
26+ 16 17 18 19 20
27+ 21 22 23 24 25
benchmarks/Auto/T/BigSum.hsnew file+22
@@ -0,0 +1,22 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+
3+module Auto.T.BigSum where
4+
5+import Control.DeepSeq
6+import Data.Aeson.TH
7+import Options
8+
9+data BigSum = F01 | F02 | F03 | F04 | F05
10+ | F06 | F07 | F08 | F09 | F10
11+ | F11 | F12 | F13 | F14 | F15
12+ | F16 | F17 | F18 | F19 | F20
13+ | F21 | F22 | F23 | F24 | F25
14+ deriving (Show, Eq)
15+
16+instance NFData BigSum where
17+ rnf a = a `seq` ()
18+
19+deriveJSON opts ''BigSum
20+
21+bigSum :: BigSum
22+bigSum = F25
benchmarks/Auto/T/D.hsnew file+38
@@ -0,0 +1,38 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+
3+module Auto.T.D where
4+
5+import Control.DeepSeq
6+import Data.Aeson.TH
7+import Options
8+
9+data D a = Nullary
10+ | Unary Int
11+ | Product String Char a
12+ | Record { testOne :: Double
13+ , testTwo :: Bool
14+ , testThree :: D a
15+ }
16+ deriving (Show, Eq)
17+
18+instance NFData a => NFData (D a) where
19+ rnf Nullary = ()
20+ rnf (Unary n) = rnf n
21+ rnf (Product s c x) = s `deepseq` c `deepseq` rnf x
22+ rnf (Record a b y) = a `deepseq` b `deepseq` rnf y
23+
24+deriveJSON opts ''D
25+
26+type T = D (D (D ()))
27+
28+d :: T
29+d = Record
30+ { testOne = 1234.56789
31+ , testTwo = True
32+ , testThree = Product "Hello World!" 'a'
33+ Record
34+ { testOne = 9876.54321
35+ , testTwo = False
36+ , testThree = Product "Yeehaa!!!" '\n' Nullary
37+ }
38+ }
benchmarks/AutoCompare.hsnew file+73
@@ -0,0 +1,73 @@
1+{-# LANGUAGE RankNTypes #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+
4+module Main (main) where
5+
6+import Control.DeepSeq
7+import Control.Monad
8+import Criterion.Main
9+import Data.Aeson
10+
11+import qualified Auto.T.D as T
12+import qualified Auto.T.BigRecord as T
13+import qualified Auto.T.BigProduct as T
14+import qualified Auto.T.BigSum as T
15+import qualified Auto.G.D as G
16+import qualified Auto.G.BigRecord as G
17+import qualified Auto.G.BigProduct as G
18+import qualified Auto.G.BigSum as G
19+
20+--------------------------------------------------------------------------------
21+
22+runBench :: IO ()
23+runBench = defaultMain
24+ [ compareBench "D" T.d G.d
25+ , compareBench "BigRecord" T.bigRecord G.bigRecord
26+ , compareBench "BigProduct" T.bigProduct G.bigProduct
27+ , compareBench "BigSum" T.bigSum G.bigSum
28+ ]
29+
30+group :: String -> Benchmarkable -> Benchmarkable -> Benchmark
31+group n th gen = bgroup n [ bench "th" th
32+ , bench "generic" gen
33+ ]
34+
35+compareBench
36+ :: forall a b
37+ . (ToJSON a, FromJSON a, NFData a, ToJSON b, FromJSON b, NFData b)
38+ => String -> a -> b -> Benchmark
39+compareBench name a b = v `deepseq` bgroup name
40+ [ group "toJSON" (nf toJSON a)
41+ (nf toJSON b)
42+ , group "encode" (nf encode a)
43+ (nf encode b)
44+ , group "fromJSON" (nf (fromJSON :: Value -> Result a) v)
45+ (nf (fromJSON :: Value -> Result b) v)
46+ ] where
47+ v = toJSON a -- == toJSON b
48+
49+sanityCheck :: IO ()
50+sanityCheck = do
51+ check T.d
52+ check G.d
53+ check T.bigRecord
54+ check G.bigRecord
55+ check T.bigProduct
56+ check G.bigProduct
57+ check T.bigSum
58+ check G.bigSum
59+
60+check :: (Show a, Eq a, FromJSON a, ToJSON a)
61+ => a -> IO ()
62+check x = do
63+ unless (Success x == (fromJSON . toJSON) x) $ fail $ "toJSON: " ++ show x
64+ unless (Success x == (decode_ . encode) x) $ fail $ "encode: " ++ show x
65+ where
66+ decode_ s = case decode s of
67+ Just v -> fromJSON v
68+ Nothing -> fail ""
69+
70+main :: IO ()
71+main = do
72+ sanityCheck
73+ runBench
benchmarks/Compare.hs+10−5
@@ -1,22 +1,25 @@
1−{-# LANGUAGE PackageImports #-}
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE ScopedTypeVariables #-}
3
4
4module Main (main) where
5module Main (main) where
5
6
6−import Prelude ()
7import Prelude.Compat
7import Prelude.Compat
8
8
9import Compare.BufferBuilder ()
9import Compare.BufferBuilder ()
10−import Compare.JsonBuilder ()
11import Criterion.Main
10import Criterion.Main
12import Data.BufferBuilder.Json
11import Data.BufferBuilder.Json
13−import Data.Json.Builder
14import Twitter
12import Twitter
15import Twitter.Manual ()
13import Twitter.Manual ()
16import Typed.Common
14import Typed.Common
17−import qualified "aeson-benchmarks" Data.Aeson as Aeson
15+import qualified Data.Aeson as Aeson
18import qualified Compare.JsonBench as JsonBench
16import qualified Compare.JsonBench as JsonBench
19
17
18+#ifdef MIN_VERSION_json_builder
19+import Data.Json.Builder
20+import Compare.JsonBuilder ()
21+#endif
22+
20main :: IO ()
23main :: IO ()
21main =
24main =
22 defaultMain [
25 defaultMain [
@@ -24,7 +27,9 @@ main =
24 bgroup "twitter" [
27 bgroup "twitter" [
25 bench "aeson" $ nf Aeson.encode twtr
28 bench "aeson" $ nf Aeson.encode twtr
26 , bench "buffer-builder" $ nf encodeJson twtr
29 , bench "buffer-builder" $ nf encodeJson twtr
30+#ifdef MIN_VERSION_json_builder
27 , bench "json-builder" $ nf toJsonLBS twtr
31 , bench "json-builder" $ nf toJsonLBS twtr
32+#endif
28 ]
33 ]
29 , JsonBench.benchmarks
34 , JsonBench.benchmarks
30 ]
35 ]
benchmarks/Compare/BufferBuilder.hs+2−3
@@ -1,3 +1,4 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE RecordWildCards #-}
3{-# LANGUAGE RecordWildCards #-}
3
4
@@ -6,13 +7,11 @@
6
7
7module Compare.BufferBuilder () where
8module Compare.BufferBuilder () where
8
9
9−import Prelude ()
10+import Prelude.Compat hiding ((<>))
10−import Prelude.Compat
11
11
12import Data.BufferBuilder.Json
12import Data.BufferBuilder.Json
13import Data.Int (Int64)
13import Data.Int (Int64)
14import Data.Monoid ((<>))
14import Data.Monoid ((<>))
15−import Prelude hiding (id)
16import Twitter
15import Twitter
17import qualified Data.BufferBuilder.Utf8 as UB
16import qualified Data.BufferBuilder.Utf8 as UB
18
17
benchmarks/Compare/JsonBench.hs+18−9
@@ -1,17 +1,18 @@
1−-- Adapted from a buffer-builder benchmark:
1+{-# LANGUAGE CPP #-}
2−--
3−-- https://github.com/chadaustin/buffer-builder/blob/master/test.json
4−
5{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE BangPatterns #-}
6{-# LANGUAGE MagicHash #-}
3{-# LANGUAGE MagicHash #-}
4+{-# LANGUAGE NoImplicitPrelude #-}
7{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE OverloadedStrings #-}
8{-# LANGUAGE RecordWildCards #-}
6{-# LANGUAGE RecordWildCards #-}
9{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE ScopedTypeVariables #-}
10
8
9+-- Adapted from a buffer-builder benchmark:
10+--
11+-- https://github.com/chadaustin/buffer-builder/blob/master/test.json
12+
11module Compare.JsonBench (benchmarks) where
13module Compare.JsonBench (benchmarks) where
12
14
13−import Prelude ()
15+import Prelude.Compat hiding ((<>))
14−import Prelude.Compat
15
16
16import Control.DeepSeq (NFData(..))
17import Control.DeepSeq (NFData(..))
17import Criterion
18import Criterion
@@ -19,9 +20,13 @@ import Data.Aeson ((.:))
19import Data.Monoid ((<>))
20import Data.Monoid ((<>))
20import Data.Text (Text)
21import Data.Text (Text)
21import Typed.Common (load)
22import Typed.Common (load)
23+import qualified Control.Monad.Fail as Fail
22import qualified Data.Aeson as Aeson
24import qualified Data.Aeson as Aeson
23import qualified Data.BufferBuilder.Json as Json
25import qualified Data.BufferBuilder.Json as Json
26+
27+#ifdef MIN_VERSION_json_builder
24import qualified Data.Json.Builder as JB
28import qualified Data.Json.Builder as JB
29+#endif
25
30
26data EyeColor = Green | Blue | Brown
31data EyeColor = Green | Blue | Brown
27 deriving (Eq, Show)
32 deriving (Eq, Show)
@@ -64,10 +69,10 @@ instance NFData Gender where rnf !_ = ()
64instance NFData Fruit where rnf !_ = ()
69instance NFData Fruit where rnf !_ = ()
65
70
66instance NFData Friend where
71instance NFData Friend where
67− rnf Friend {..} = rnf fId `seq` rnf fName `seq` ()
72+ rnf Friend {..} = rnf fId `seq` rnf fName
68
73
69instance NFData User where
74instance NFData User where
70− rnf User {..} = rnf uId `seq` rnf uIndex `seq` rnf uGuid `seq` rnf uIsActive `seq` rnf uBalance `seq` rnf uPicture `seq` rnf uAge `seq` rnf uEyeColor `seq` rnf uName `seq` rnf uGender `seq` rnf uCompany `seq` rnf uEmail `seq` rnf uPhone `seq` rnf uAddress `seq` rnf uAbout `seq` rnf uRegistered `seq` rnf uLatitude `seq` rnf uLongitude `seq` rnf uTags `seq` rnf uFriends `seq` rnf uGreeting `seq` rnf uFavouriteFruit `seq` ()
75+ rnf User {..} = rnf uId `seq` rnf uIndex `seq` rnf uGuid `seq` rnf uIsActive `seq` rnf uBalance `seq` rnf uPicture `seq` rnf uAge `seq` rnf uEyeColor `seq` rnf uName `seq` rnf uGender `seq` rnf uCompany `seq` rnf uEmail `seq` rnf uPhone `seq` rnf uAddress `seq` rnf uAbout `seq` rnf uRegistered `seq` rnf uLatitude `seq` rnf uLongitude `seq` rnf uTags `seq` rnf uFriends `seq` rnf uGreeting `seq` rnf uFavouriteFruit
71
76
72eyeColorTable :: [(Text, EyeColor)]
77eyeColorTable :: [(Text, EyeColor)]
73eyeColorTable = [("brown", Brown), ("green", Green), ("blue", Blue)]
78eyeColorTable = [("brown", Brown), ("green", Green), ("blue", Blue)]
@@ -78,7 +83,7 @@ genderTable = [("male", Male), ("female", Female)]
78fruitTable :: [(Text, Fruit)]
83fruitTable :: [(Text, Fruit)]
79fruitTable = [("apple", Apple), ("strawberry", Strawberry), ("banana", Banana)]
84fruitTable = [("apple", Apple), ("strawberry", Strawberry), ("banana", Banana)]
80
85
81−enumFromJson :: Monad m => String -> [(Text, enum)] -> (json -> m Text) -> json -> m enum
86+enumFromJson :: Fail.MonadFail m => String -> [(Text, enum)] -> (json -> m Text) -> json -> m enum
82enumFromJson enumName table extract v = do
87enumFromJson enumName table extract v = do
83 s <- extract v
88 s <- extract v
84 case lookup s table of
89 case lookup s table of
@@ -268,6 +273,7 @@ instance Json.ToJson User where
268 <> "greeting"# Json..=# uGreeting
273 <> "greeting"# Json..=# uGreeting
269 <> "favoriteFruit"# Json..=# uFavouriteFruit
274 <> "favoriteFruit"# Json..=# uFavouriteFruit
270
275
276+#ifdef MIN_VERSION_json_builder
271---- json-builder instances ----
277---- json-builder instances ----
272
278
273instance JB.Value EyeColor where
279instance JB.Value EyeColor where
@@ -319,6 +325,7 @@ instance JB.Value User where
319 <> t "friends" `JB.row` uFriends
325 <> t "friends" `JB.row` uFriends
320 <> t "greeting" `JB.row` uGreeting
326 <> t "greeting" `JB.row` uGreeting
321 <> t "favoriteFruit" `JB.row` uFavouriteFruit
327 <> t "favoriteFruit" `JB.row` uFavouriteFruit
328+#endif
322
329
323benchmarks :: Benchmark
330benchmarks :: Benchmark
324benchmarks = env (load "json-data/buffer-builder.json") $
331benchmarks = env (load "json-data/buffer-builder.json") $
@@ -326,5 +333,7 @@ benchmarks = env (load "json-data/buffer-builder.json") $
326 bgroup "json-bench" [
333 bgroup "json-bench" [
327 bench "aeson" $ nf Aeson.encode parsedUserList
334 bench "aeson" $ nf Aeson.encode parsedUserList
328 , bench "buffer-builder" $ nf Json.encodeJson parsedUserList
335 , bench "buffer-builder" $ nf Json.encodeJson parsedUserList
336+#ifdef MIN_VERSION_json_builder
329 , bench "json-builder" $ nf JB.toJsonLBS parsedUserList
337 , bench "json-builder" $ nf JB.toJsonLBS parsedUserList
338+#endif
330 ]
339 ]
benchmarks/Compare/JsonBuilder.hs+2−3
@@ -1,3 +1,4 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1{-# LANGUAGE RecordWildCards #-}
2{-# LANGUAGE RecordWildCards #-}
2
3
3{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
4{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
@@ -5,12 +6,10 @@
5
6
6module Compare.JsonBuilder () where
7module Compare.JsonBuilder () where
7
8
8−import Prelude ()
9+import Prelude.Compat hiding ((<>))
9−import Prelude.Compat
10
10
11import Data.Json.Builder
11import Data.Json.Builder
12import Data.Monoid ((<>))
12import Data.Monoid ((<>))
13−import Prelude hiding (id)
14import Twitter
13import Twitter
15
14
16instance JsObject Metadata where
15instance JsObject Metadata where
benchmarks/CompareWithJSON.hs+23−33
@@ -1,9 +1,9 @@
1−{-# LANGUAGE PackageImports #-}
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
2{-# OPTIONS_GHC -fno-warn-orphans #-}
3{-# OPTIONS_GHC -fno-warn-orphans #-}
3
4
4module Main (main) where
5module Main (main) where
5
6
6−import Prelude ()
7import Prelude.Compat
7import Prelude.Compat
8
8
9import Blaze.ByteString.Builder (toLazyByteString)
9import Blaze.ByteString.Builder (toLazyByteString)
@@ -11,10 +11,9 @@ import Blaze.ByteString.Builder.Char.Utf8 (fromString)
11import Control.DeepSeq (NFData(rnf))
11import Control.DeepSeq (NFData(rnf))
12import Criterion.Main
12import Criterion.Main
13import Data.Maybe (fromMaybe)
13import Data.Maybe (fromMaybe)
14−import qualified "aeson-benchmarks" Data.Aeson as A
14+import qualified Data.Aeson as A
15−import qualified "aeson-benchmarks" Data.Aeson.Text as A
15+import qualified Data.Aeson.Text as A
16−import qualified "aeson-benchmarks" Data.Aeson.Parser.Internal as I
16+import qualified Data.Aeson.Parser.Internal as I
17−import qualified "aeson" Data.Aeson as B
18import qualified Data.ByteString as BS
17import qualified Data.ByteString as BS
19import qualified Data.ByteString.Lazy as BL
18import qualified Data.ByteString.Lazy as BL
20import qualified Data.Text.Lazy as TL
19import qualified Data.Text.Lazy as TL
@@ -28,7 +27,7 @@ instance (NFData v) => NFData (J.JSObject v) where
28instance NFData J.JSValue where
27instance NFData J.JSValue where
29 rnf J.JSNull = ()
28 rnf J.JSNull = ()
30 rnf (J.JSBool b) = rnf b
29 rnf (J.JSBool b) = rnf b
31− rnf (J.JSRational a b) = rnf a `seq` rnf b `seq` ()
30+ rnf (J.JSRational a b) = rnf a `seq` rnf b
32 rnf (J.JSString s) = rnf (J.fromJSString s)
31 rnf (J.JSString s) = rnf (J.fromJSString s)
33 rnf (J.JSArray lst) = rnf lst
32 rnf (J.JSArray lst) = rnf lst
34 rnf (J.JSObject o) = rnf o
33 rnf (J.JSObject o) = rnf o
@@ -39,20 +38,14 @@ decodeJ s =
39 J.Ok v -> v
38 J.Ok v -> v
40 J.Error _ -> error "fail to parse via JSON"
39 J.Error _ -> error "fail to parse via JSON"
41
40
42−decodeA :: BL.ByteString -> A.Value
41+decode :: BL.ByteString -> A.Value
43−decodeA s = fromMaybe (error "fail to parse via Aeson") $ A.decode s
44−
45−decodeA' :: BL.ByteString -> A.Value
46−decodeA' s = fromMaybe (error "fail to parse via Aeson") $ A.decode' s
42+decode s = fromMaybe (error "fail to parse via Aeson") $ A.decode s
47−
48−decodeAS :: BS.ByteString -> A.Value
49−decodeAS s = fromMaybe (error "fail to parse via Aeson") $ A.decodeStrict' s
50
43
51−decodeB :: BL.ByteString -> B.Value
44+decode' :: BL.ByteString -> A.Value
52−decodeB s = fromMaybe (error "fail to parse via Aeson") $ B.decode s
45+decode' s = fromMaybe (error "fail to parse via Aeson") $ A.decode' s
53
46
54−decodeBS :: BS.ByteString -> B.Value
47+decodeS :: BS.ByteString -> A.Value
55−decodeBS s = fromMaybe (error "fail to parse via Aeson") $ B.decodeStrict' s
48+decodeS s = fromMaybe (error "fail to parse via Aeson") $ A.decodeStrict' s
56
49
57decodeIP :: BL.ByteString -> A.Value
50decodeIP :: BL.ByteString -> A.Value
58decodeIP s = fromMaybe (error "fail to parse via Parser.decodeWith") $
51decodeIP s = fromMaybe (error "fail to parse via Parser.decodeWith") $
@@ -80,32 +73,29 @@ main = do
80 defaultMain [
73 defaultMain [
81 bgroup "decode" [
74 bgroup "decode" [
82 bgroup "en" [
75 bgroup "en" [
83− bench "aeson/lazy" $ nf decodeA enA
76+ bench "aeson/lazy" $ nf decode enA
84− , bench "aeson/strict" $ nf decodeA' enA
77+ , bench "aeson/strict" $ nf decode' enA
85− , bench "aeson/stricter" $ nf decodeAS enS
78+ , bench "aeson/stricter" $ nf decodeS enS
86− , bench "aeson/hackage" $ nf decodeB enA
87− , bench "aeson/hackage'" $ nf decodeBS enS
88 , bench "aeson/parser" $ nf decodeIP enA
79 , bench "aeson/parser" $ nf decodeIP enA
89 , bench "json" $ nf decodeJ enJ
80 , bench "json" $ nf decodeJ enJ
90 ]
81 ]
91 , bgroup "jp" [
82 , bgroup "jp" [
92− bench "aeson" $ nf decodeA jpA
83+ bench "aeson" $ nf decode jpA
93− , bench "aeson/stricter" $ nf decodeAS jpS
84+ , bench "aeson/stricter" $ nf decodeS jpS
94− , bench "aeson/hackage" $ nf decodeB jpA
95 , bench "json" $ nf decodeJ jpJ
85 , bench "json" $ nf decodeJ jpJ
96 ]
86 ]
97 ]
87 ]
98 , bgroup "encode" [
88 , bgroup "encode" [
99 bgroup "en" [
89 bgroup "en" [
100− bench "aeson-to-bytestring" $ nf A.encode (decodeA enA)
90+ bench "aeson-to-bytestring" $ nf A.encode (decode enA)
101− , bench "aeson-via-text-to-bytestring" $ nf encodeViaText (decodeA enA)
91+ , bench "aeson-via-text-to-bytestring" $ nf encodeViaText (decode enA)
102− , bench "aeson-to-text" $ nf encodeToText (decodeA enA)
92+ , bench "aeson-to-text" $ nf encodeToText (decode enA)
103 , bench "json" $ nf encodeJ (decodeJ enJ)
93 , bench "json" $ nf encodeJ (decodeJ enJ)
104 ]
94 ]
105 , bgroup "jp" [
95 , bgroup "jp" [
106− bench "aeson-to-bytestring" $ nf A.encode (decodeA jpA)
96+ bench "aeson-to-bytestring" $ nf A.encode (decode jpA)
107− , bench "aeson-via-text-to-bytestring" $ nf encodeViaText (decodeA jpA)
97+ , bench "aeson-via-text-to-bytestring" $ nf encodeViaText (decode jpA)
108− , bench "aeson-to-text" $ nf encodeToText (decodeA jpA)
98+ , bench "aeson-to-text" $ nf encodeToText (decode jpA)
109 , bench "json" $ nf encodeJ (decodeJ jpJ)
99 , bench "json" $ nf encodeJ (decodeJ jpJ)
110 ]
100 ]
111 ]
101 ]
benchmarks/Dates.hs+2−2
@@ -1,7 +1,7 @@
1−{-# OPTIONS_GHC -fsimpl-tick-factor=0 #-}
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
2module Main (main) where
3module Main (main) where
3
4
4−import Prelude ()
5import Prelude.Compat
5import Prelude.Compat
6
6
7import Criterion.Main
7import Criterion.Main
benchmarks/Escape.hsnew file+32
@@ -0,0 +1,32 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+
4+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
5+
6+module Main (main) where
7+
8+import Prelude.Compat
9+
10+import Criterion.Main
11+import qualified Data.Aeson.Parser.UnescapeFFI as FFI
12+import qualified Data.Aeson.Parser.UnescapePure as Pure
13+
14+import qualified Data.ByteString.Char8 as BS
15+import System.Environment (getArgs, withArgs)
16+
17+main :: IO ()
18+main = do
19+ args_ <- getArgs
20+ let (args, p, n) =
21+ case args_ of
22+ "--pattern" : p : args_ -> k p args_
23+ _ -> k "\\\"" args_
24+ k p args_ =
25+ case args_ of
26+ "--repeat" : n : args_ -> (args_, p, read n)
27+ args_ -> (args_, p, 10000)
28+ input = BS.concat $ replicate n $ BS.pack p
29+ withArgs args $ defaultMain
30+ [ bench "ffi" $ whnf FFI.unescapeText input
31+ , bench "pure" $ whnf Pure.unescapeText input
32+ ]
benchmarks/Issue673.hsnew file+165
@@ -0,0 +1,165 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+
4+module Main (
5+ main,
6+ input17,
7+ input32,
8+ input64,
9+ input128,
10+ input256,
11+ input2048,
12+ input4096,
13+ input8192,
14+ input16384,
15+ ) where
16+
17+import Criterion.Main
18+import Prelude.Compat
19+import Data.Int (Int64)
20+import Data.Scientific (Scientific)
21+import Data.Semigroup ((<>))
22+import Data.Aeson.Parser (scientific)
23+
24+import qualified Data.Attoparsec.ByteString.Lazy as AttoL
25+import qualified Data.Attoparsec.ByteString.Char8 as Atto8
26+import qualified Data.Aeson as A
27+import qualified Data.ByteString as BS
28+import qualified Data.ByteString.Lazy as LBS
29+import qualified Data.ByteString.Lazy.Char8 as LBS8
30+
31+decodeInt :: LBS.ByteString -> Maybe Int
32+decodeInt = A.decode
33+
34+decodeString :: LBS.ByteString -> Maybe String
35+decodeString = A.decode
36+
37+decodeScientific :: LBS.ByteString -> Maybe Scientific
38+decodeScientific = A.decode
39+
40+decodeViaRead :: LBS.ByteString -> Integer
41+decodeViaRead = read . LBS8.unpack
42+
43+decodeAtto :: LBS.ByteString -> Maybe Scientific
44+decodeAtto
45+ = parseOnly (scientific <* AttoL.endOfInput)
46+ where
47+ parseOnly p lbs = case AttoL.parse p lbs of
48+ AttoL.Done _ r -> Just r
49+ AttoL.Fail {} -> Nothing
50+
51+decodeAtto8 :: LBS.ByteString -> Maybe Scientific
52+decodeAtto8
53+ = parseOnly (Atto8.scientific <* AttoL.endOfInput)
54+ where
55+ parseOnly p lbs = case AttoL.parse p lbs of
56+ AttoL.Done _ r -> Just r
57+ AttoL.Fail {} -> Nothing
58+
59+generate :: Int64 -> LBS.ByteString
60+generate n = LBS8.replicate n '1'
61+
62+input17 :: LBS.ByteString
63+input17 = generate 17
64+
65+input32 :: LBS.ByteString
66+input32 = generate 32
67+
68+input64 :: LBS.ByteString
69+input64 = generate 64
70+
71+input128 :: LBS.ByteString
72+input128 = generate 128
73+
74+input256 :: LBS.ByteString
75+input256 = generate 256
76+
77+input2048 :: LBS.ByteString
78+input2048 = generate 2048
79+
80+input4096 :: LBS.ByteString
81+input4096 = generate 4096
82+
83+input8192 :: LBS.ByteString
84+input8192 = generate 8192
85+
86+input16384 :: LBS.ByteString
87+input16384 = generate 16384
88+
89+
90+main :: IO ()
91+main = defaultMain
92+ -- works on 64bit
93+ [ benchPair "17" input17
94+ -- , benchPair "32" input32
95+ -- , benchPair "64" input64
96+ -- , benchPair "128" input128
97+ -- , benchPair "256" input256
98+ , benchPair "2048" input2048
99+ , benchPair "4096" input4096
100+ , benchPair "8192" input8192
101+ , benchPair "16384" input16384
102+ ]
103+ where
104+ benchPair name input = bgroup name
105+ [ bench "Int" $ whnf decodeInt input
106+ , bench "Simple" $ whnf bsToIntegerSimple (LBS.toStrict input)
107+ , bench "Optim" $ whnf bsToInteger (LBS.toStrict input)
108+ , bench "Read" $ whnf decodeViaRead input
109+ , bench "Scientific" $ whnf decodeScientific input
110+ , bench "parserA" $ whnf decodeAtto input
111+ , bench "parserS" $ whnf decodeAtto8 input
112+ , bench "String" $ whnf decodeString $ "\"" <> input <> "\""
113+ ]
114+
115+-------------------------------------------------------------------------------
116+-- better fromInteger
117+-------------------------------------------------------------------------------
118+
119+bsToInteger :: BS.ByteString -> Integer
120+bsToInteger bs
121+ | l > 40 = valInteger 10 l [ fromIntegral (w - 48) | w <- BS.unpack bs ]
122+ | otherwise = bsToIntegerSimple bs
123+ where
124+ l = BS.length bs
125+
126+bsToIntegerSimple :: BS.ByteString -> Integer
127+bsToIntegerSimple = BS.foldl' step 0 where
128+ step a b = a * 10 + fromIntegral (b - 48) -- 48 = '0'
129+
130+-- A sub-quadratic algorithm for Integer. Pairs of adjacent radix b
131+-- digits are combined into a single radix b^2 digit. This process is
132+-- repeated until we are left with a single digit. This algorithm
133+-- performs well only on large inputs, so we use the simple algorithm
134+-- for smaller inputs.
135+valInteger :: Integer -> Int -> [Integer] -> Integer
136+valInteger = go
137+ where
138+ go :: Integer -> Int -> [Integer] -> Integer
139+ go _ _ [] = 0
140+ go _ _ [d] = d
141+ go b l ds
142+ | l > 40 = b' `seq` go b' l' (combine b ds')
143+ | otherwise = valSimple b ds
144+ where
145+ -- ensure that we have an even number of digits
146+ -- before we call combine:
147+ ds' = if even l then ds else 0 : ds
148+ b' = b * b
149+ l' = (l + 1) `quot` 2
150+
151+ combine b (d1 : d2 : ds) = d `seq` (d : combine b ds)
152+ where
153+ d = d1 * b + d2
154+ combine _ [] = []
155+ combine _ [_] = errorWithoutStackTrace "this should not happen"
156+
157+-- The following algorithm is only linear for types whose Num operations
158+-- are in constant time.
159+valSimple :: Integer -> [Integer] -> Integer
160+valSimple base = go 0
161+ where
162+ go r [] = r
163+ go r (d : ds) = r' `seq` go r' ds
164+ where
165+ r' = r * base + fromIntegral d
benchmarks/JsonParse.hs+3−2
@@ -1,10 +1,11 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE ScopedTypeVariables #-}
4+
3{-# OPTIONS_GHC -fno-warn-orphans #-}
5{-# OPTIONS_GHC -fno-warn-orphans #-}
4
6
5module Main (main) where
7module Main (main) where
6
8
7−import Prelude ()
8import Prelude.Compat
9import Prelude.Compat
9
10
10import Control.DeepSeq
11import Control.DeepSeq
@@ -16,7 +17,7 @@ import Text.JSON
16instance NFData JSValue where
17instance NFData JSValue where
17 rnf JSNull = ()
18 rnf JSNull = ()
18 rnf (JSBool b) = rnf b
19 rnf (JSBool b) = rnf b
19− rnf (JSRational b r) = rnf b `seq` rnf r `seq` ()
20+ rnf (JSRational b r) = rnf b `seq` rnf r
20 rnf (JSString s) = rnf (fromJSString s)
21 rnf (JSString s) = rnf (fromJSString s)
21 rnf (JSArray vs) = rnf vs
22 rnf (JSArray vs) = rnf vs
22 rnf (JSObject kvs) = rnf (fromJSObject kvs)
23 rnf (JSObject kvs) = rnf (fromJSObject kvs)
benchmarks/Micro.hs+8−6
@@ -1,11 +1,13 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
1module Main (main) where
3module Main (main) where
2
4
3−import Prelude ()
4import Prelude.Compat
5import Prelude.Compat
5
6
6import Criterion.Main
7import Criterion.Main
7−import qualified Data.Aeson.Encoding.Builder as AB
8+
8−import qualified Data.ByteString.Builder as B
9+-- Encoding is a newtype wrapper around Builder
10+import Data.Aeson.Encoding (text, string, encodingToLazyByteString)
9import qualified Data.Text as T
11import qualified Data.Text as T
10
12
11main :: IO ()
13main :: IO ()
@@ -13,8 +15,8 @@ main = do
13 let txt = "append (append b (primBounded w1 x1)) (primBounded w2 x2)"
15 let txt = "append (append b (primBounded w1 x1)) (primBounded w2 x2)"
14 defaultMain [
16 defaultMain [
15 bgroup "string" [
17 bgroup "string" [
16− bench "text" $ nf (B.toLazyByteString . AB.text) (T.pack txt)
18+ bench "text" $ nf (encodingToLazyByteString . text) (T.pack txt)
17− , bench "string direct" $ nf (B.toLazyByteString . AB.string) txt
19+ , bench "string direct" $ nf (encodingToLazyByteString . string) txt
18− , bench "string via text" $ nf (B.toLazyByteString . AB.text . T.pack) txt
20+ , bench "string via text" $ nf (encodingToLazyByteString . text . T.pack) txt
19 ]
21 ]
20 ]
22 ]
benchmarks/Options.hs−3
@@ -1,8 +1,5 @@
1module Options (opts) where
1module Options (opts) where
2
2
3−import Prelude ()
4−import Prelude.Compat
5−
6import Data.Aeson.Types
3import Data.Aeson.Types
7
4
8opts :: Options
5opts :: Options
benchmarks/ReadFile.hs+1−1
@@ -1,9 +1,9 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
3
4
4module Main (main) where
5module Main (main) where
5
6
6−import Prelude ()
7import Prelude.Compat
7import Prelude.Compat
8
8
9import Control.DeepSeq
9import Control.DeepSeq
benchmarks/Typed.hs+2−1
@@ -1,6 +1,7 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
1module Main (main) where
3module Main (main) where
2
4
3−import Prelude ()
4import Prelude.Compat
5import Prelude.Compat
5
6
6import Criterion.Main
7import Criterion.Main
benchmarks/Typed/Common.hs+1−10
@@ -1,25 +1,16 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE NoImplicitPrelude #-}
2−#ifdef HAS_BOTH_AESON_AND_BENCHMARKS
3−{-# LANGUAGE PackageImports #-}
4−#endif
5
2
6{-# OPTIONS_GHC -fno-warn-unused-imports #-}
3{-# OPTIONS_GHC -fno-warn-unused-imports #-}
7
4
8module Typed.Common (load) where
5module Typed.Common (load) where
9
6
10−import Prelude ()
11import Prelude.Compat
7import Prelude.Compat
12
8
13import Data.ByteString.Lazy as L
9import Data.ByteString.Lazy as L
14import System.Exit
10import System.Exit
15import System.IO
11import System.IO
16
12
17−#ifndef HAS_BOTH_AESON_AND_BENCHMARKS
18import Data.Aeson hiding (Result)
13import Data.Aeson hiding (Result)
19−#else
20−import "aeson" Data.Aeson hiding (Result)
21−import qualified "aeson-benchmarks" Data.Aeson as B
22−#endif
23
14
24load :: FromJSON a => FilePath -> IO a
15load :: FromJSON a => FilePath -> IO a
25load fileName = do
16load fileName = do
benchmarks/Typed/Generic.hs+15−31
@@ -1,60 +1,44 @@
1−{-# LANGUAGE PackageImports #-}
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
2module Typed.Generic (benchmarks, decodeBenchmarks) where
3module Typed.Generic (benchmarks, decodeBenchmarks) where
3
4
4−import Prelude ()
5import Prelude.Compat
5import Prelude.Compat
6
6
7−import "aeson" Data.Aeson hiding (Result)
7+import Data.Aeson hiding (Result)
8import Criterion
8import Criterion
9import Data.ByteString.Lazy as L
9import Data.ByteString.Lazy as L
10import Twitter.Generic
10import Twitter.Generic
11import Typed.Common
11import Typed.Common
12−import qualified "aeson-benchmarks" Data.Aeson as B
13−
14−encodeDirectA :: Result -> L.ByteString
15−encodeDirectA = encode
16
12
17−encodeViaValueA :: Result -> L.ByteString
13+encodeDirect :: Result -> L.ByteString
18−encodeViaValueA = encode . toJSON
14+encodeDirect = encode
19
15
20−encodeDirectB :: Result -> L.ByteString
21−encodeDirectB = B.encode
22−
23−encodeViaValueB :: Result -> L.ByteString
16+encodeViaValue :: Result -> L.ByteString
24−encodeViaValueB = B.encode . B.toJSON
17+encodeViaValue = encode . toJSON
25
18
26benchmarks :: Benchmark
19benchmarks :: Benchmark
27benchmarks =
20benchmarks =
28 env ((,) <$> load "json-data/twitter100.json" <*> load "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
21 env ((,) <$> load "json-data/twitter100.json" <*> load "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
29 bgroup "encodeGeneric" [
22 bgroup "encodeGeneric" [
30 bgroup "direct" [
23 bgroup "direct" [
31− bench "twitter100" $ nf encodeDirectB twitter100
24+ bench "twitter100" $ nf encodeDirect twitter100
32− , bench "jp100" $ nf encodeDirectB jp100
33− , bench "twitter100 baseline" $ nf encodeDirectA twitter100
34− , bench "jp100 baseline" $ nf encodeDirectA jp100
25+ , bench "jp100" $ nf encodeDirect jp100
35 ]
26 ]
36 , bgroup "viaValue" [
27 , bgroup "viaValue" [
37− bench "twitter100" $ nf encodeViaValueB twitter100
28+ bench "twitter100" $ nf encodeViaValue twitter100
38− , bench "jp100" $ nf encodeViaValueB jp100
39− , bench "twitter100 baseline" $ nf encodeViaValueA twitter100
40− , bench "jp100 baseline" $ nf encodeViaValueA jp100
29+ , bench "jp100" $ nf encodeViaValue jp100
41 ]
30 ]
42 ]
31 ]
43
32
44−decodeDirectA :: L.ByteString -> Maybe Result
45−decodeDirectA = decode
46−
47−decodeDirectB :: L.ByteString -> Maybe Result
33+decodeDirect :: L.ByteString -> Maybe Result
48−decodeDirectB = B.decode
34+decodeDirect = decode
49
35
50decodeBenchmarks :: Benchmark
36decodeBenchmarks :: Benchmark
51decodeBenchmarks =
37decodeBenchmarks =
52 env ((,) <$> L.readFile "json-data/twitter100.json" <*> L.readFile "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
38 env ((,) <$> L.readFile "json-data/twitter100.json" <*> L.readFile "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
53 bgroup "decodeGeneric"
39 bgroup "decodeGeneric"
54 [ bgroup "direct"
40 [ bgroup "direct"
55− [ bench "twitter100" $ nf decodeDirectB twitter100
41+ [ bench "twitter100" $ nf decodeDirect twitter100
56− , bench "jp100" $ nf decodeDirectB jp100
57− , bench "twitter100 baseline" $ nf decodeDirectA twitter100
58− , bench "jp100 baseline" $ nf decodeDirectA jp100
42+ , bench "jp100" $ nf decodeDirect jp100
59 ]
43 ]
60 ]
44 ]
benchmarks/Typed/Manual.hs+15−31
@@ -1,60 +1,44 @@
1−{-# LANGUAGE PackageImports #-}
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
2module Typed.Manual (benchmarks, decodeBenchmarks) where
3module Typed.Manual (benchmarks, decodeBenchmarks) where
3
4
4−import Prelude ()
5import Prelude.Compat
5import Prelude.Compat
6
6
7−import "aeson" Data.Aeson hiding (Result)
7+import Data.Aeson hiding (Result)
8import Criterion
8import Criterion
9import Data.ByteString.Lazy as L
9import Data.ByteString.Lazy as L
10import Twitter.Manual
10import Twitter.Manual
11import Typed.Common
11import Typed.Common
12−import qualified "aeson-benchmarks" Data.Aeson as B
13−
14−encodeDirectA :: Result -> L.ByteString
15−encodeDirectA = encode
16
12
17−encodeViaValueA :: Result -> L.ByteString
13+encodeDirect :: Result -> L.ByteString
18−encodeViaValueA = encode . toJSON
14+encodeDirect = encode
19
15
20−encodeDirectB :: Result -> L.ByteString
21−encodeDirectB = B.encode
22−
23−encodeViaValueB :: Result -> L.ByteString
16+encodeViaValue :: Result -> L.ByteString
24−encodeViaValueB = B.encode . B.toJSON
17+encodeViaValue = encode . toJSON
25
18
26benchmarks :: Benchmark
19benchmarks :: Benchmark
27benchmarks =
20benchmarks =
28 env ((,) <$> load "json-data/twitter100.json" <*> load "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
21 env ((,) <$> load "json-data/twitter100.json" <*> load "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
29 bgroup "encodeManual" [
22 bgroup "encodeManual" [
30 bgroup "direct" [
23 bgroup "direct" [
31− bench "twitter100" $ nf encodeDirectB twitter100
24+ bench "twitter100" $ nf encodeDirect twitter100
32− , bench "jp100" $ nf encodeDirectB jp100
33− , bench "twitter100 baseline" $ nf encodeDirectA twitter100
34− , bench "jp100 baseline" $ nf encodeDirectA jp100
25+ , bench "jp100" $ nf encodeDirect jp100
35 ]
26 ]
36 , bgroup "viaValue" [
27 , bgroup "viaValue" [
37− bench "twitter100" $ nf encodeViaValueB twitter100
28+ bench "twitter100" $ nf encodeViaValue twitter100
38− , bench "jp100" $ nf encodeViaValueB jp100
39− , bench "twitter100 baseline" $ nf encodeViaValueA twitter100
40− , bench "jp100 baseline" $ nf encodeViaValueA jp100
29+ , bench "jp100" $ nf encodeViaValue jp100
41 ]
30 ]
42 ]
31 ]
43
32
44−decodeDirectA :: L.ByteString -> Maybe Result
45−decodeDirectA = decode
46−
47−decodeDirectB :: L.ByteString -> Maybe Result
33+decodeDirect :: L.ByteString -> Maybe Result
48−decodeDirectB = B.decode
34+decodeDirect = decode
49
35
50decodeBenchmarks :: Benchmark
36decodeBenchmarks :: Benchmark
51decodeBenchmarks =
37decodeBenchmarks =
52 env ((,) <$> L.readFile "json-data/twitter100.json" <*> L.readFile "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
38 env ((,) <$> L.readFile "json-data/twitter100.json" <*> L.readFile "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
53 bgroup "decodeManual"
39 bgroup "decodeManual"
54 [ bgroup "direct"
40 [ bgroup "direct"
55− [ bench "twitter100" $ nf decodeDirectB twitter100
41+ [ bench "twitter100" $ nf decodeDirect twitter100
56− , bench "jp100" $ nf decodeDirectB jp100
57− , bench "twitter100 baseline" $ nf decodeDirectA twitter100
58− , bench "jp100 baseline" $ nf decodeDirectA jp100
42+ , bench "jp100" $ nf decodeDirect jp100
59 ]
43 ]
60 ]
44 ]
benchmarks/Typed/TH.hs+15−31
@@ -1,60 +1,44 @@
1−{-# LANGUAGE PackageImports #-}
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
2module Typed.TH (benchmarks, decodeBenchmarks) where
3module Typed.TH (benchmarks, decodeBenchmarks) where
3
4
4−import Prelude ()
5import Prelude.Compat
5import Prelude.Compat
6
6
7−import "aeson" Data.Aeson hiding (Result)
7+import Data.Aeson hiding (Result)
8import Criterion
8import Criterion
9import Data.ByteString.Lazy as L
9import Data.ByteString.Lazy as L
10import Twitter.TH
10import Twitter.TH
11import Typed.Common
11import Typed.Common
12−import qualified "aeson-benchmarks" Data.Aeson as B
13−
14−encodeDirectA :: Result -> L.ByteString
15−encodeDirectA = encode
16
12
17−encodeViaValueA :: Result -> L.ByteString
13+encodeDirect :: Result -> L.ByteString
18−encodeViaValueA = encode . toJSON
14+encodeDirect = encode
19
15
20−encodeDirectB :: Result -> L.ByteString
21−encodeDirectB = B.encode
22−
23−encodeViaValueB :: Result -> L.ByteString
16+encodeViaValue :: Result -> L.ByteString
24−encodeViaValueB = B.encode . B.toJSON
17+encodeViaValue = encode . toJSON
25
18
26benchmarks :: Benchmark
19benchmarks :: Benchmark
27benchmarks =
20benchmarks =
28 env ((,) <$> load "json-data/twitter100.json" <*> load "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
21 env ((,) <$> load "json-data/twitter100.json" <*> load "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
29 bgroup "encodeTH" [
22 bgroup "encodeTH" [
30 bgroup "direct" [
23 bgroup "direct" [
31− bench "twitter100" $ nf encodeDirectB twitter100
24+ bench "twitter100" $ nf encodeDirect twitter100
32− , bench "jp100" $ nf encodeDirectB jp100
33− , bench "twitter100 baseline" $ nf encodeDirectA twitter100
34− , bench "jp100 baseline" $ nf encodeDirectA jp100
25+ , bench "jp100" $ nf encodeDirect jp100
35 ]
26 ]
36 , bgroup "viaValue" [
27 , bgroup "viaValue" [
37− bench "twitter100" $ nf encodeViaValueA twitter100
28+ bench "twitter100" $ nf encodeViaValue twitter100
38− , bench "jp100" $ nf encodeViaValueA jp100
39− , bench "twitter100 baseline" $ nf encodeViaValueB twitter100
40− , bench "jp100 baseline" $ nf encodeViaValueB jp100
29+ , bench "jp100" $ nf encodeViaValue jp100
41 ]
30 ]
42 ]
31 ]
43
32
44−decodeDirectA :: L.ByteString -> Maybe Result
45−decodeDirectA = decode
46−
47−decodeDirectB :: L.ByteString -> Maybe Result
33+decodeDirect :: L.ByteString -> Maybe Result
48−decodeDirectB = B.decode
34+decodeDirect = decode
49
35
50decodeBenchmarks :: Benchmark
36decodeBenchmarks :: Benchmark
51decodeBenchmarks =
37decodeBenchmarks =
52 env ((,) <$> L.readFile "json-data/twitter100.json" <*> L.readFile "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
38 env ((,) <$> L.readFile "json-data/twitter100.json" <*> L.readFile "json-data/jp100.json") $ \ ~(twitter100, jp100) ->
53 bgroup "decodeTH"
39 bgroup "decodeTH"
54 [ bgroup "direct"
40 [ bgroup "direct"
55− [ bench "twitter100" $ nf decodeDirectB twitter100
41+ [ bench "twitter100" $ nf decodeDirect twitter100
56− , bench "jp100" $ nf decodeDirectB jp100
57− , bench "twitter100 baseline" $ nf decodeDirectA twitter100
58− , bench "jp100 baseline" $ nf decodeDirectA jp100
42+ , bench "jp100" $ nf decodeDirect jp100
59 ]
43 ]
60 ]
44 ]
benchmarks/aeson-benchmarks.cabal+166−70
@@ -1,66 +1,21 @@
1name: aeson-benchmarks
1name: aeson-benchmarks
2version: 0
2version: 0
3build-type: Simple
3build-type: Simple
4−
5−cabal-version: >=1.8
4+cabal-version: >=1.10
6
5
7flag bytestring-builder
6flag bytestring-builder
8 description: Depend on the bytestring-builder package for backwards compatibility.
7 description: Depend on the bytestring-builder package for backwards compatibility.
9 default: False
8 default: False
10 manual: False
9 manual: False
11
10
11+flag local-aeson
12+ description: Build the local version of aeson, to avoid rebuilding aeson's
13+ reverse dependencies for benchmarking (statistics, criterion).
14+ default: True
15+ manual: True
16+
12library
17library
13 default-language: Haskell2010
18 default-language: Haskell2010
14− hs-source-dirs: .. . ../ffi ../pure ../attoparsec-iso8601
15− c-sources: ../cbits/unescape_string.c
16− exposed-modules:
17− Data.Aeson
18− Data.Aeson.Encoding
19− Data.Aeson.Encoding.Builder
20− Data.Aeson.Encoding.Internal
21− Data.Aeson.Internal
22− Data.Aeson.Internal.Functions
23− Data.Aeson.Internal.Time
24− Data.Aeson.Parser
25− Data.Aeson.Parser.Internal
26− Data.Aeson.Parser.Time
27− Data.Aeson.Parser.Unescape
28− Data.Aeson.Parser.UnescapeFFI
29− Data.Aeson.Parser.UnescapePure
30− Data.Aeson.TH
31− Data.Aeson.Text
32− Data.Aeson.Types
33− Data.Aeson.Types.Class
34− Data.Aeson.Types.FromJSON
35− Data.Aeson.Types.Generic
36− Data.Aeson.Types.Internal
37− Data.Aeson.Types.ToJSON
38− Data.Attoparsec.Time
39− Data.Attoparsec.Time.Internal
40−
41− build-depends:
42− attoparsec >= 0.13.0.1,
43− base == 4.*,
44− base-compat >= 0.9.1 && <0.10,
45− time-locale-compat >=0.1.1 && <0.2,
46− containers,
47− deepseq,
48− dlist >= 0.2,
49− fail == 4.9.*,
50− ghc-prim >= 0.2,
51− hashable >= 1.1.2.0,
52− mtl,
53− scientific >= 0.3.4.7 && < 0.4,
54− syb,
55− tagged >=0.8.3 && <0.9,
56− template-haskell >= 2.4,
57− text >= 1.1.1.0,
58− th-abstraction >= 0.2.2 && < 0.3,
59− time,
60− transformers,
61− unordered-containers >= 0.2.3.0,
62− uuid-types >= 1.0.3 && <1.1,
63− vector >= 0.7.1
64
19
65 if flag(bytestring-builder)
20 if flag(bytestring-builder)
66 build-depends: bytestring >= 0.9 && < 0.10.4,
21 build-depends: bytestring >= 0.9 && < 0.10.4,
@@ -68,26 +23,139 @@ library
68 else
23 else
69 build-depends: bytestring >= 0.10.4
24 build-depends: bytestring >= 0.10.4
70
25
26+ if flag(local-aeson)
27+
28+ hs-source-dirs: .. ../ffi ../pure ../attoparsec-iso8601
29+ c-sources: ../cbits/unescape_string.c
30+ build-depends:
31+ attoparsec,
32+ base,
33+ base-compat,
34+ containers,
35+ deepseq,
36+ dlist,
37+ fail,
38+ ghc-prim,
39+ hashable,
40+ mtl,
41+ primitive,
42+ scientific,
43+ syb,
44+ tagged,
45+ template-haskell,
46+ text,
47+ th-abstraction,
48+ time-compat,
49+ time,
50+ transformers,
51+ unordered-containers,
52+ uuid-types,
53+ vector
54+
71− if impl(ghc >=7.8)
55+ if !impl(ghc >= 8.6)
56+ build-depends: contravariant
57+
58+ if !impl(ghc >= 8.0)
59+ -- `Data.Semigroup` is available in base only since GHC 8.0 / base 4.9
60+ build-depends: semigroups,
61+ transformers-compat
62+
63+ if !impl(ghc >= 7.10)
64+ -- `Numeric.Natural` is available in base only since GHC 7.10 / base 4.8
65+ build-depends: nats,
66+ void
67+
68+ include-dirs: ../include
69+
70+ ghc-options: -O2 -Wall
72− cpp-options: -DHAS_COERCIBLE
71+ cpp-options: -DGENERICS
73
72
74− if !impl(ghc >= 8.0)
73+ exposed-modules:
75− -- `Data.Semigroup` is available in base only since GHC 8.0 / base 4.9
74+ Data.Aeson
76− build-depends: semigroups >= 0.18.2 && < 0.19
75+ Data.Aeson.Encoding
76+ Data.Aeson.Encoding.Builder
77+ Data.Aeson.Encoding.Internal
78+ Data.Aeson.Internal
79+ Data.Aeson.Internal.Functions
80+ Data.Aeson.Internal.Time
81+ Data.Aeson.Parser
82+ Data.Aeson.Parser.Internal
83+ Data.Aeson.Parser.Time
84+ Data.Aeson.Parser.Unescape
85+ Data.Aeson.Parser.UnescapeFFI
86+ Data.Aeson.Parser.UnescapePure
87+ Data.Aeson.TH
88+ Data.Aeson.Text
89+ Data.Aeson.Types
90+ Data.Aeson.Types.Class
91+ Data.Aeson.Types.FromJSON
92+ Data.Aeson.Types.Generic
93+ Data.Aeson.Types.Internal
94+ Data.Aeson.Types.ToJSON
95+ Data.Attoparsec.Time
96+ Data.Attoparsec.Time.Internal
77
97
78− include-dirs: ../include
98+ else
99+
100+ build-depends: aeson
101+
102+ reexported-modules:
103+ Data.Aeson,
104+ Data.Aeson.Encoding,
105+ Data.Aeson.Parser.Internal,
106+ Data.Aeson.Text,
107+ Data.Aeson.TH,
108+ Data.Aeson.Types
79
109
110+executable aeson-benchmark-auto-compare
111+ default-language: Haskell2010
112+ main-is: AutoCompare.hs
113+ hs-source-dirs: .
80− ghc-options: -O2 -Wall
114+ ghc-options: -Wall -O2 -rtsopts
81− cpp-options: -DGENERICS
115+ other-modules:
116+ Auto.T.D
117+ Auto.T.BigRecord
118+ Auto.T.BigProduct
119+ Auto.T.BigSum
120+ Auto.G.D
121+ Auto.G.BigRecord
122+ Auto.G.BigProduct
123+ Auto.G.BigSum
124+ Options
125+ build-depends:
126+ aeson-benchmarks,
127+ base,
128+ criterion,
129+ deepseq,
130+ template-haskell
131+
132+executable aeson-benchmark-escape
133+ default-language: Haskell2010
134+ main-is: Escape.hs
135+ hs-source-dirs: ../examples .
136+ ghc-options: -Wall -O2 -rtsopts
137+ if flag(local-aeson)
138+ build-depends:
139+ aeson-benchmarks,
140+ base,
141+ base-compat,
142+ bytestring,
143+ criterion >= 1.0,
144+ deepseq,
145+ ghc-prim,
146+ text
147+ else
148+ -- Disabled because of inaccessible internals
149+ buildable: False
82
150
83executable aeson-benchmark-compare
151executable aeson-benchmark-compare
152+ default-language: Haskell2010
84 main-is: Compare.hs
153 main-is: Compare.hs
85 hs-source-dirs: ../examples .
154 hs-source-dirs: ../examples .
86 ghc-options: -Wall -O2 -rtsopts
155 ghc-options: -Wall -O2 -rtsopts
87 other-modules:
156 other-modules:
88 Compare.BufferBuilder
157 Compare.BufferBuilder
89 Compare.JsonBench
158 Compare.JsonBench
90− Compare.JsonBuilder
91 Twitter
159 Twitter
92 Twitter.Manual
160 Twitter.Manual
93 Typed.Common
161 Typed.Common
@@ -100,10 +168,19 @@ executable aeson-benchmark-compare
100 criterion >= 1.0,
168 criterion >= 1.0,
101 deepseq,
169 deepseq,
102 ghc-prim,
170 ghc-prim,
103− json-builder,
104 text
171 text
105
172
173+ if impl(ghc < 8.4)
174+ other-modules:
175+ Compare.JsonBuilder
176+ build-depends:
177+ json-builder
178+ if impl(ghc < 8.0)
179+ build-depends:
180+ semigroups
181+
106executable aeson-benchmark-micro
182executable aeson-benchmark-micro
183+ default-language: Haskell2010
107 main-is: Micro.hs
184 main-is: Micro.hs
108 hs-source-dirs: ../examples .
185 hs-source-dirs: ../examples .
109 ghc-options: -Wall -O2 -rtsopts
186 ghc-options: -Wall -O2 -rtsopts
@@ -118,12 +195,10 @@ executable aeson-benchmark-micro
118 text
195 text
119
196
120executable aeson-benchmark-typed
197executable aeson-benchmark-typed
198+ default-language: Haskell2010
121 main-is: Typed.hs
199 main-is: Typed.hs
122 hs-source-dirs: ../examples .
200 hs-source-dirs: ../examples .
123 ghc-options: -Wall -O2 -rtsopts
201 ghc-options: -Wall -O2 -rtsopts
124− -- We must help ourself in situations when there is both
125− -- aeson and aeson-benchmakrs
126− cpp-options: -DHAS_BOTH_AESON_AND_BENCHMARKS
127 other-modules:
202 other-modules:
128 Twitter
203 Twitter
129 Twitter.Generic
204 Twitter.Generic
@@ -135,7 +210,6 @@ executable aeson-benchmark-typed
135 Typed.Manual
210 Typed.Manual
136 Typed.TH
211 Typed.TH
137 build-depends:
212 build-depends:
138− aeson,
139 aeson-benchmarks,
213 aeson-benchmarks,
140 base,
214 base,
141 base-compat,
215 base-compat,
@@ -150,13 +224,14 @@ executable aeson-benchmark-typed
150 bytestring-builder >= 0.10.4 && < 1
224 bytestring-builder >= 0.10.4 && < 1
151 else
225 else
152 build-depends: bytestring >= 0.10.4
226 build-depends: bytestring >= 0.10.4
227+ if impl(ghc < 8.0)
228+ build-depends: semigroups
153
229
154executable aeson-benchmark-compare-with-json
230executable aeson-benchmark-compare-with-json
231+ default-language: Haskell2010
155 main-is: CompareWithJSON.hs
232 main-is: CompareWithJSON.hs
156 ghc-options: -Wall -O2 -rtsopts
233 ghc-options: -Wall -O2 -rtsopts
157− cpp-options: -DHAS_BOTH_AESON_AND_BENCHMARKS
158 build-depends:
234 build-depends:
159− aeson,
160 aeson-benchmarks,
235 aeson-benchmarks,
161 base,
236 base,
162 base-compat,
237 base-compat,
@@ -168,6 +243,7 @@ executable aeson-benchmark-compare-with-json
168 text
243 text
169
244
170executable aeson-benchmark-aeson-encode
245executable aeson-benchmark-aeson-encode
246+ default-language: Haskell2010
171 main-is: AesonEncode.hs
247 main-is: AesonEncode.hs
172 ghc-options: -Wall -O2 -rtsopts
248 ghc-options: -Wall -O2 -rtsopts
173 build-depends:
249 build-depends:
@@ -180,6 +256,7 @@ executable aeson-benchmark-aeson-encode
180 time
256 time
181
257
182executable aeson-benchmark-aeson-parse
258executable aeson-benchmark-aeson-parse
259+ default-language: Haskell2010
183 main-is: AesonParse.hs
260 main-is: AesonParse.hs
184 ghc-options: -Wall -O2 -rtsopts
261 ghc-options: -Wall -O2 -rtsopts
185 build-depends:
262 build-depends:
@@ -191,6 +268,7 @@ executable aeson-benchmark-aeson-parse
191 time
268 time
192
269
193executable aeson-benchmark-json-parse
270executable aeson-benchmark-json-parse
271+ default-language: Haskell2010
194 main-is: JsonParse.hs
272 main-is: JsonParse.hs
195 ghc-options: -Wall -O2 -rtsopts
273 ghc-options: -Wall -O2 -rtsopts
196 build-depends:
274 build-depends:
@@ -201,6 +279,7 @@ executable aeson-benchmark-json-parse
201 time
279 time
202
280
203executable aeson-benchmark-dates
281executable aeson-benchmark-dates
282+ default-language: Haskell2010
204 main-is: Dates.hs
283 main-is: Dates.hs
205 ghc-options: -Wall -O2 -rtsopts
284 ghc-options: -Wall -O2 -rtsopts
206 build-depends:
285 build-depends:
@@ -212,12 +291,14 @@ executable aeson-benchmark-dates
212 aeson-benchmarks,
291 aeson-benchmarks,
213 text,
292 text,
214 time
293 time
294+ if impl(ghc >= 8.2)
295+ ghc-options: -Wno-simplifiable-class-constraints
215
296
216executable aeson-benchmark-map
297executable aeson-benchmark-map
298+ default-language: Haskell2010
217 main-is: AesonMap.hs
299 main-is: AesonMap.hs
218 ghc-options: -Wall -O2 -rtsopts
300 ghc-options: -Wall -O2 -rtsopts
219 build-depends:
301 build-depends:
220− aeson,
221 aeson-benchmarks,
302 aeson-benchmarks,
222 base,
303 base,
223 base-compat,
304 base-compat,
@@ -231,10 +312,10 @@ executable aeson-benchmark-map
231 unordered-containers
312 unordered-containers
232
313
233executable aeson-benchmark-foldable
314executable aeson-benchmark-foldable
315+ default-language: Haskell2010
234 main-is: AesonFoldable.hs
316 main-is: AesonFoldable.hs
235 ghc-options: -Wall -O2 -rtsopts
317 ghc-options: -Wall -O2 -rtsopts
236 build-depends:
318 build-depends:
237− aeson,
238 aeson-benchmarks,
319 aeson-benchmarks,
239 base,
320 base,
240 base-compat,
321 base-compat,
@@ -247,3 +328,18 @@ executable aeson-benchmark-foldable
247 text,
328 text,
248 unordered-containers,
329 unordered-containers,
249 vector
330 vector
331+
332+executable aeson-issue-673
333+ default-language: Haskell2010
334+ main-is: Issue673.hs
335+ ghc-options: -Wall -O1 -rtsopts
336+ build-depends:
337+ aeson-benchmarks,
338+ attoparsec,
339+ base,
340+ bytestring,
341+ scientific,
342+ base-compat,
343+ criterion >= 1.0
344+ if impl(ghc < 8.0)
345+ build-depends: semigroups
cabal.project+3
@@ -1,2 +1,5 @@
1with-compiler: ghc
1with-compiler: ghc
2packages: .
2packages: .
3+packages: attoparsec-iso8601
4+-- packages: benchmarks
5+tests: true
changelog.md+152−6
@@ -1,13 +1,159 @@
1For 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).
1For 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).
2
2
3−## Upcoming
3+### upcoming
4
4
5−* Add `FromJSON` and `ToJSON` instances for `DiffTime`, thanks to Víctor López Juan
5+* `FromJSON` instance of `Ratio a` now parses numbers in addtion to
6−* Fix handling of fractions when parsing Natural.
6+ standard `{numerator=..., denumerator=...}` encoding.
7− Change text in error messages for Integral types to make them follow the common pattern.
8− PVP level is "Non-breaking change".
9
7
8+### 1.4.6.0
9+
10+* Provide a clearer error message when a required tagKey for a constructor is missing, thanks to Guru Devanla.
11+ The error message now looks like this: `Error in $: parsing Types.SomeType failed, expected Object with key "tag" containing one of ["nullary","unary","product","record","list"], key "tag" not found`
12+
13+* Add `formatPath` and `formatRelativePath` functions to turn a `JSONPath` into a `String`, thanks to Robbie McMichael
14+
15+
16+### 1.4.5.0
17+
18+* Expose `(<?>)`, `JSONPath` and `JSONPathElement(..)` from `Data.Aeson.Types`. Previously only available through internal modules. Thanks to Luke Clifton.
19+
20+* Support for base-compat 0.11, thanks to Ryan Scott.
21+
22+* Travis build for GHC 8.8, thanks to Oleg Grenrus.
23+
24+### 1.4.4.0
25+
26+**New features**:
27+
28+* Adds a parameterized parser `jsonWith` that can be used to choose how to handle duplicate keys in objects, thanks to Xia Li-Yao.
29+
30+* Add generic implementations of `FromJSONKey` and `ToJSONKey`, thanks to Xia Li-Yao. Example:
31+
32+```haskell
33+data Foo = Bar
34+ deriving Generic
35+
36+opts :: JSONKeyOptions
37+opts = defaultJSONKeyOptions { keyModifier = toLower }
38+
39+instance ToJSONKey Foo where
40+ toJSONKey = genericToJSONKey opts
41+
42+instance FromJSONKey Foo where
43+ fromJSONKey = genericFromJSONKey opts
44+```
45+
46+**Minor**:
47+* aeson now uses `time-compat` instead of `time-locale-compat`, thanks to Oleg Grenrus.
48+* Prepare for `MonadFail` breakages in GHC 8.8, thanks to Oleg Grenrus.
49+* Require `bytestring >= 0.10.8.1` for newer GHCs to avoid build failures, thanks to Oleg Grenrus.
50+* Support `primitive 0.7.*`, thanks to Adam Bergmark.
51+* Allow `semigroups 0.19.*` and `hashable 1.3.*`, thanks to Oleg Grenrus.
52+* Fix a typo in the error message when parsing `NonEmpty`, thanks to Colin Woodbury.
53+* Document surprising behavior when using `omitNothingFields` with type variables, thanks to Xia Li-Yao.
54+
55+**Internal changes**:
56+* Code cleanup by Oleg Grenrus
57+* Fix dependencies of the benchmarks on older GHC's, thanks to Xia Li-Yao.
58+
59+### 1.4.3.0
60+* Improve error messages for FromJSON in existing instances and GHC Generic implementation. Thanks to Xia Li-Yao & Igor Pashev.
61+* Tweak error-reporting combinators and their documentation. Thanks to Xia Li-Yao.
62+ * `typeMismatch` is now about comparing JSON types (i.e., the expected and actual names of the Value constructor).
63+ * `withObject` and other `with*` combinators now also mention the JSON types they expect
64+ * New `unexpected` and `prependFailure` combinators.
65+* Add `Contravariant` `ToJSONKeyFunction` instance. Thanks to Oleg Grenrus.
66+* Add `KeyValue` instance for `Object`. Thanks to Robert Hensing.
67+* Improve performance when parsing certain large numbers, thanks to Oleg Grenrus.
68+* Add `Data.Aeson.QQ.Simple` - A limited version of aeson-qq. Thanks to Oleg Grenrus.
69+* Exposes internal helper functions like `<?>`, `JSONPath`, and `parseIndexedJSON` from `Data.Aeson` module. Thanks to Abid Uzair.
70+* Better error messages when there are syntax errors parsing objects and arrays. Thanks to Fintan Halpenny.
71+* Support building with `th-abstraction-0.3.0.0` or later. Thanks to Ryan Scott.
72+
73+### 1.4.2.0
74+
75+* Add `Data.Aeson.QQ.Simple` which is a simpler version of the `aeson-qq` package, it does not support interpolation, thanks to Oleg Grenrus.
76+* Add `Contravariant ToJSONKeyFunction` instance, thanks to Oleg Grenrus.
77+* Add `KeyValue Object` instance, thanks to Robert Hensing
78+* Improved performance when parsing large numbers, thanks to Oleg Grenrus.
79+
80+### 1.4.1.0
81+
82+* Optimizations of generics, thanks to Rémy Oudompheng, here are some numbers for GHC 8.4:
83+ * Compilation time: G/BigProduct.hs is 25% faster, G/BigRecord.hs is 2x faster.
84+ * Runtime performance: BigRecord/toJSON/generic and BigProduct/encode/generic are more than 2x faster.
85+* Added To/FromJSON instances for `Void` and Generics's `V1`, thanks to Will Yager
86+* Added To/FromJSON instances for `primitive`'s `Array`, `SmallArray`, `PrimArray` and `UnliftedArray`, thanks to Andrew Thad.
87+* Fixes handling of `UTCTime` wrt. leap seconds , thanks to Adam Schønemann
88+* Warning and documentation fixes thanks to tom-bop, Gabor Greif, Ian Jeffries, and Mateusz Curyło.
89+
90+## 1.4.0.0
91+
92+This release introduces bounds on the size of `Scientific` numbers when they are converted to other arbitrary precision types that do not represent them efficiently in memory.
93+
94+This means that trying to decode a number such as `1e1000000000` into an `Integer` will now fail instead of using a lot of memory. If you need to represent large numbers you can add a newtype (preferably over `Scientific`) and providing a parser using `withScientific`.
95+
96+The following instances are affected by this:
97+* `FromJSON Natural`
98+* `FromJSONKey Natural`
99+* `FromJSON Integer`
100+* `FromJSONKey Integer`
101+* `FromJSON NominalDiffTime`
102+
103+For the same reasons the following instances & functions have been removed:
104+* Remove `FromJSON Data.Attoparsec.Number` instance. Note that `Data.Attoparsec.Number` is deprecated.
105+* Remove deprecated `withNumber`, use `withScientific` instead.
106+
107+Finally, encoding integral values with large exponents now uses scientific notation, this saves space for large numbers.
108+
109+#### 1.3.1.1
110+
111+* Catch 0 denominators when parsing Ratio
112+
113+### 1.3.1.0
114+
115+* Fix bug in generically derived `FromJSON` instances that are using `unwrapUnaryRecords`, thanks to Xia Li-yao
116+* Allow base-compat 0.10.*, thanks to Oleg Grenrus
117+
118+## 1.3.0.0
119+
120+Breaking changes:
121+* `GKeyValue` has been renamed to `KeyValuePair`, thanks to Xia Li-yao
122+* Removed unused `FromJSON` constraint in `withEmbeddedJson`, thanks to Tristan Seligmann
123+
124+Other improvements:
125+* Optimizations of TH toEncoding, thanks to Xia Li-yao
126+* Optimizations of hex decoding when using the default/pure unescape implementation, thanks to Xia Li-yao
127+* Improved error message on `Day` parse failures, thanks to Gershom Bazerman
128+* Add `encodeFile` as well as `decodeFile*` variants, thanks to Markus Hauck
129+* Documentation fixes, thanks to Lennart Spitzner
130+* CPP cleanup, thanks to Ryan Scott
131+
132+### 1.2.4.0
133+
134+* Add `Ord` instance for `JSONPathElement`, thanks to Simon Hengel.
135+
136+
137+### 1.2.3.0
138+
139+* Added `withEmbeddedJSON` to help parse JSON embedded inside a JSON string, thanks to Jesse Kempf.
140+* Memory usage improvements to the default (pure) parser, thanks to Jonathan Paugh. Also thanks to Neil Mitchell & Oleg Grenrus for contributing a benchmark.
141+* `omitNothingFields` now works for the `Option` newtype, thanks to Xia Li-yao.
142+* Some documentation fixes, thanks to Jonathan Paug & Philippe Crama.
143+
144+### 1.2.2.0
145+
146+* Add `FromJSON` and `ToJSON` instances for
147+ * `DiffTime`, thanks to Víctor López Juan.
148+ * `CTime`, thanks to Daniel Díaz.
149+* Fix handling of fractions when parsing Natural, thanks to Yuriy Syrovetskiy.
150+* Change text in error messages for Integral types to make them follow the common pattern, thanks to Yuriy Syrovetskiy.
151+* Add missing `INCOHERENT` pragma for `RecordToPair`, thanks to Xia Li-yao.
152+* Everything related to `Options` is now exported from `Data.Aeson`, thanks to Xia Li-yao.
153+* Optimizations to not escape text in clear cases, thanks to Oleg Grenrus.
154+* Some documentation fixes, thanks to Phil de Joux & Xia Li-yao.
155+
10−## 1.2.1.0
156+### 1.2.1.0
11
157
12* Add `parserThrowError` and `parserCatchError` combinators, thanks to Oleg Grenrus.
158* Add `parserThrowError` and `parserCatchError` combinators, thanks to Oleg Grenrus.
13
159
examples/Generic.hs+1−1
@@ -6,11 +6,11 @@
6-- automatically derive the Generic class for us.
6-- automatically derive the Generic class for us.
7
7
8{-# LANGUAGE DeriveGeneric #-}
8{-# LANGUAGE DeriveGeneric #-}
9+{-# LANGUAGE NoImplicitPrelude #-}
9{-# LANGUAGE OverloadedStrings #-}
10{-# LANGUAGE OverloadedStrings #-}
10
11
11module Main (main) where
12module Main (main) where
12
13
13−import Prelude ()
14import Prelude.Compat
14import Prelude.Compat
15
15
16import Data.Aeson (FromJSON, ToJSON, decode, encode)
16import Data.Aeson (FromJSON, ToJSON, decode, encode)
examples/Simplest.hs+1−1
@@ -1,9 +1,9 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE RecordWildCards #-}
3{-# LANGUAGE RecordWildCards #-}
3
4
4module Main (main) where
5module Main (main) where
5
6
6−import Prelude ()
7import Prelude.Compat
7import Prelude.Compat
8
8
9import Control.Applicative (empty)
9import Control.Applicative (empty)
examples/TemplateHaskell.hs+1−1
@@ -1,12 +1,12 @@
1-- We can use Template Haskell (TH) to generate instances of the
1-- We can use Template Haskell (TH) to generate instances of the
2-- FromJSON and ToJSON classes automatically. This is the fastest way
2-- FromJSON and ToJSON classes automatically. This is the fastest way
3-- to add JSON support for a type.
3-- to add JSON support for a type.
4+{-# LANGUAGE NoImplicitPrelude #-}
4{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE TemplateHaskell #-}
6{-# LANGUAGE TemplateHaskell #-}
6
7
7module Main (main) where
8module Main (main) where
8
9
9−import Prelude ()
10import Prelude.Compat
10import Prelude.Compat
11
11
12import Data.Aeson (decode, encode)
12import Data.Aeson (decode, encode)
examples/Twitter.hs+1−2
@@ -10,6 +10,7 @@
10
10
11{-# LANGUAGE DeriveDataTypeable #-}
11{-# LANGUAGE DeriveDataTypeable #-}
12{-# LANGUAGE DeriveGeneric #-}
12{-# LANGUAGE DeriveGeneric #-}
13+{-# LANGUAGE NoImplicitPrelude #-}
13
14
14module Twitter
15module Twitter
15 (
16 (
@@ -19,7 +20,6 @@ module Twitter
19 , Result(..)
20 , Result(..)
20 ) where
21 ) where
21
22
22−import Prelude ()
23import Prelude.Compat
23import Prelude.Compat
24
24
25import Control.DeepSeq
25import Control.DeepSeq
@@ -27,7 +27,6 @@ import Data.Data (Typeable, Data)
27import Data.Int (Int64)
27import Data.Int (Int64)
28import Data.Text (Text)
28import Data.Text (Text)
29import GHC.Generics (Generic)
29import GHC.Generics (Generic)
30−import Prelude hiding (id)
31
30
32{-# ANN module "Hlint: ignore Use camelCase" #-}
31{-# ANN module "Hlint: ignore Use camelCase" #-}
33{-# ANN module "Hlint: ignore Use newtype instead of data" #-}
32{-# ANN module "Hlint: ignore Use newtype instead of data" #-}
examples/Twitter/Generic.hs+2−37
@@ -1,11 +1,8 @@
1-- Use GHC generics to automatically generate good instances.
1-- Use GHC generics to automatically generate good instances.
2
2
3−{-# LANGUAGE CPP #-}
3+{-# LANGUAGE NoImplicitPrelude #-}
4−{-# OPTIONS_GHC -fno-warn-orphans #-}
5
4
6−#ifdef HAS_BOTH_AESON_AND_BENCHMARKS
5+{-# OPTIONS_GHC -fno-warn-orphans #-}
7−{-# LANGUAGE PackageImports #-}
8−#endif
9
6
10module Twitter.Generic
7module Twitter.Generic
11 (
8 (
@@ -15,18 +12,12 @@ module Twitter.Generic
15 , Result(..)
12 , Result(..)
16 ) where
13 ) where
17
14
18−import Prelude ()
19import Prelude.Compat ()
15import Prelude.Compat ()
20
16
21import Twitter
17import Twitter
22import Twitter.Options
18import Twitter.Options
23
19
24−#ifndef HAS_BOTH_AESON_AND_BENCHMARKS
25import Data.Aeson (ToJSON (..), FromJSON (..), genericToJSON, genericToEncoding, genericParseJSON)
20import Data.Aeson (ToJSON (..), FromJSON (..), genericToJSON, genericToEncoding, genericParseJSON)
26−#else
27−import "aeson" Data.Aeson (ToJSON (..), FromJSON (..), genericToJSON, genericToEncoding, genericParseJSON)
28−import qualified "aeson-benchmarks" Data.Aeson as B
29−#endif
30
21
31instance ToJSON Metadata where
22instance ToJSON Metadata where
32 toJSON = genericToJSON twitterOptions
23 toJSON = genericToJSON twitterOptions
@@ -51,29 +42,3 @@ instance ToJSON Result where
51 toEncoding = genericToEncoding twitterOptions
42 toEncoding = genericToEncoding twitterOptions
52instance FromJSON Result where
43instance FromJSON Result where
53 parseJSON = genericParseJSON twitterOptions
44 parseJSON = genericParseJSON twitterOptions
54−
55−#ifdef HAS_BOTH_AESON_AND_BENCHMARKS
56−instance B.ToJSON Metadata where
57− toJSON = B.genericToJSON btwitterOptions
58− toEncoding = B.genericToEncoding btwitterOptions
59−instance B.FromJSON Metadata where
60− parseJSON = B.genericParseJSON btwitterOptions
61−
62−instance B.ToJSON Geo where
63− toJSON = B.genericToJSON btwitterOptions
64− toEncoding = B.genericToEncoding btwitterOptions
65−instance B.FromJSON Geo where
66− parseJSON = B.genericParseJSON btwitterOptions
67−
68−instance B.ToJSON Story where
69− toJSON = B.genericToJSON btwitterOptions
70− toEncoding = B.genericToEncoding btwitterOptions
71−instance B.FromJSON Story where
72− parseJSON = B.genericParseJSON btwitterOptions
73−
74−instance B.ToJSON Result where
75− toJSON = B.genericToJSON btwitterOptions
76− toEncoding = B.genericToEncoding btwitterOptions
77−instance B.FromJSON Result where
78− parseJSON = B.genericParseJSON btwitterOptions
79−#endif
examples/Twitter/Manual.hs+3−139
@@ -1,13 +1,9 @@
1-- Manually write instances.
1-- Manually write instances.
2−
3−{-# LANGUAGE CPP #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
4{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE RecordWildCards #-}
4{-# LANGUAGE RecordWildCards #-}
6−{-# OPTIONS_GHC -fno-warn-orphans #-}
7
5
8−#ifdef HAS_BOTH_AESON_AND_BENCHMARKS
6+{-# OPTIONS_GHC -fno-warn-orphans #-}
9−{-# LANGUAGE PackageImports #-}
10−#endif
11
7
12module Twitter.Manual
8module Twitter.Manual
13 (
9 (
@@ -17,20 +13,13 @@ module Twitter.Manual
17 , Result(..)
13 , Result(..)
18 ) where
14 ) where
19
15
20−import Prelude ()
21import Prelude.Compat
16import Prelude.Compat
22
17
23import Control.Applicative
18import Control.Applicative
24−import Data.Monoid ((<>))
19+import Data.Semigroup ((<>))
25−import Prelude hiding (id)
26import Twitter
20import Twitter
27
21
28−#ifndef HAS_BOTH_AESON_AND_BENCHMARKS
29import Data.Aeson hiding (Result)
22import Data.Aeson hiding (Result)
30−#else
31−import "aeson" Data.Aeson hiding (Result)
32−import qualified "aeson-benchmarks" Data.Aeson as B
33−#endif
34
23
35instance ToJSON Metadata where
24instance ToJSON Metadata where
36
25
@@ -155,128 +144,3 @@ instance FromJSON Result where
155 <*> v .: "max_id_str"
144 <*> v .: "max_id_str"
156 <*> v .: "query"
145 <*> v .: "query"
157 parseJSON _ = empty
146 parseJSON _ = empty
158−
159−#ifdef HAS_BOTH_AESON_AND_BENCHMARKS
160−instance B.ToJSON Metadata where
161− toJSON Metadata{..} = B.object [
162− "result_type" B..= result_type
163− ]
164−
165− toEncoding Metadata{..} = B.pairs $
166− "result_type" B..= result_type
167−
168−instance B.FromJSON Metadata where
169− parseJSON (B.Object v) = Metadata <$> v B..: "result_type"
170− parseJSON _ = empty
171−
172−instance B.ToJSON Geo where
173− toJSON Geo{..} = B.object [
174− "type_" B..= type_
175− , "coordinates" B..= coordinates
176− ]
177−
178− toEncoding Geo{..} = B.pairs $
179− "type_" B..= type_
180− <> "coordinates" B..= coordinates
181−
182−instance B.FromJSON Geo where
183− parseJSON (B.Object v) = Geo <$>
184− v B..: "type_"
185− <*> v B..: "coordinates"
186− parseJSON _ = empty
187−
188−instance B.ToJSON Story where
189− toJSON Story{..} = B.object [
190− "from_user_id_str" B..= from_user_id_str
191− , "profile_image_url" B..= profile_image_url
192− , "created_at" B..= created_at
193− , "from_user" B..= from_user
194− , "id_str" B..= id_str
195− , "metadata" B..= metadata
196− , "to_user_id" B..= to_user_id
197− , "text" B..= text
198− , "id" B..= id_
199− , "from_user_id" B..= from_user_id
200− , "geo" B..= geo
201− , "iso_language_code" B..= iso_language_code
202− , "to_user_id_str" B..= to_user_id_str
203− , "source" B..= source
204− ]
205−
206− toEncoding Story{..} = B.pairs $
207− "from_user_id_str" B..= from_user_id_str
208− <> "profile_image_url" B..= profile_image_url
209− <> "created_at" B..= created_at
210− <> "from_user" B..= from_user
211− <> "id_str" B..= id_str
212− <> "metadata" B..= metadata
213− <> "to_user_id" B..= to_user_id
214− <> "text" B..= text
215− <> "id" B..= id_
216− <> "from_user_id" B..= from_user_id
217− <> "geo" B..= geo
218− <> "iso_language_code" B..= iso_language_code
219− <> "to_user_id_str" B..= to_user_id_str
220− <> "source" B..= source
221−
222−instance B.FromJSON Story where
223− parseJSON (B.Object v) = Story <$>
224− v B..: "from_user_id_str"
225− <*> v B..: "profile_image_url"
226− <*> v B..: "created_at"
227− <*> v B..: "from_user"
228− <*> v B..: "id_str"
229− <*> v B..: "metadata"
230− <*> v B..: "to_user_id"
231− <*> v B..: "text"
232− <*> v B..: "id"
233− <*> v B..: "from_user_id"
234− <*> v B..: "geo"
235− <*> v B..: "iso_language_code"
236− <*> v B..: "to_user_id_str"
237− <*> v B..: "source"
238− parseJSON _ = empty
239−
240−instance B.ToJSON Result where
241− toJSON Result{..} = B.object [
242− "results" B..= results
243− , "max_id" B..= max_id
244− , "since_id" B..= since_id
245− , "refresh_url" B..= refresh_url
246− , "next_page" B..= next_page
247− , "results_per_page" B..= results_per_page
248− , "page" B..= page
249− , "completed_in" B..= completed_in
250− , "since_id_str" B..= since_id_str
251− , "max_id_str" B..= max_id_str
252− , "query" B..= query
253− ]
254−
255− toEncoding Result{..} = B.pairs $
256− "results" B..= results
257− <> "max_id" B..= max_id
258− <> "since_id" B..= since_id
259− <> "refresh_url" B..= refresh_url
260− <> "next_page" B..= next_page
261− <> "results_per_page" B..= results_per_page
262− <> "page" B..= page
263− <> "completed_in" B..= completed_in
264− <> "since_id_str" B..= since_id_str
265− <> "max_id_str" B..= max_id_str
266− <> "query" B..= query
267−
268−instance B.FromJSON Result where
269− parseJSON (B.Object v) = Result <$>
270− v B..: "results"
271− <*> v B..: "max_id"
272− <*> v B..: "since_id"
273− <*> v B..: "refresh_url"
274− <*> v B..: "next_page"
275− <*> v B..: "results_per_page"
276− <*> v B..: "page"
277− <*> v B..: "completed_in"
278− <*> v B..: "since_id_str"
279− <*> v B..: "max_id_str"
280− <*> v B..: "query"
281− parseJSON _ = empty
282−#endif
examples/Twitter/Options.hs−23
@@ -1,20 +1,6 @@
1−{-# LANGUAGE CPP #-}
2−
3−#ifdef HAS_BOTH_AESON_AND_BENCHMARKS
4−{-# LANGUAGE PackageImports #-}
5−#endif
6−
7module Twitter.Options (module Twitter.Options) where
1module Twitter.Options (module Twitter.Options) where
8
2
9−#ifndef HAS_BOTH_AESON_AND_BENCHMARKS
10import Data.Aeson
3import Data.Aeson
11−import Data.Aeson.Types
12−#else
13−import "aeson" Data.Aeson
14−import "aeson" Data.Aeson.Types
15−import qualified "aeson-benchmarks" Data.Aeson as B
16−import qualified "aeson-benchmarks" Data.Aeson.Types as B
17−#endif
18
4
19twitterOptions :: Options
5twitterOptions :: Options
20twitterOptions = defaultOptions
6twitterOptions = defaultOptions
@@ -22,12 +8,3 @@ twitterOptions = defaultOptions
22 "id_" -> "id"
8 "id_" -> "id"
23 _ -> x
9 _ -> x
24 }
10 }
25−
26−#ifdef HAS_BOTH_AESON_AND_BENCHMARKS
27−btwitterOptions :: B.Options
28−btwitterOptions = B.defaultOptions
29− { B.fieldLabelModifier = \x -> case x of
30− "id_" -> "id"
31− _ -> x
32− }
33−#endif
examples/Twitter/TH.hs+2−18
@@ -1,12 +1,9 @@
1-- Use Template Haskell to generate good instances.
1-- Use Template Haskell to generate good instances.
2
2
3−{-# LANGUAGE CPP #-}
3+{-# LANGUAGE NoImplicitPrelude #-}
4{-# LANGUAGE TemplateHaskell #-}
4{-# LANGUAGE TemplateHaskell #-}
5−{-# OPTIONS_GHC -fno-warn-orphans #-}
6
5
7−#ifdef HAS_BOTH_AESON_AND_BENCHMARKS
6+{-# OPTIONS_GHC -fno-warn-orphans #-}
8−{-# LANGUAGE PackageImports #-}
9−#endif
10
7
11module Twitter.TH
8module Twitter.TH
12 (
9 (
@@ -16,26 +13,13 @@ module Twitter.TH
16 , Result(..)
13 , Result(..)
17 ) where
14 ) where
18
15
19−import Prelude ()
20
16
21import Twitter
17import Twitter
22import Twitter.Options
18import Twitter.Options
23
19
24−#ifndef HAS_BOTH_AESON_AND_BENCHMARKS
25import Data.Aeson.TH
20import Data.Aeson.TH
26−#else
27−import "aeson" Data.Aeson.TH
28−import qualified "aeson-benchmarks" Data.Aeson.TH as B
29−#endif
30
21
31$(deriveJSON twitterOptions ''Metadata)
22$(deriveJSON twitterOptions ''Metadata)
32$(deriveJSON twitterOptions ''Geo)
23$(deriveJSON twitterOptions ''Geo)
33$(deriveJSON twitterOptions ''Story)
24$(deriveJSON twitterOptions ''Story)
34$(deriveJSON twitterOptions ''Result)
25$(deriveJSON twitterOptions ''Result)
35−
36−#ifdef HAS_BOTH_AESON_AND_BENCHMARKS
37−$(B.deriveJSON btwitterOptions ''Metadata)
38−$(B.deriveJSON btwitterOptions ''Geo)
39−$(B.deriveJSON btwitterOptions ''Story)
40−$(B.deriveJSON btwitterOptions ''Result)
41−#endif
ffi/Data/Aeson/Parser/UnescapeFFI.hs+1−1
@@ -10,7 +10,7 @@ module Data.Aeson.Parser.UnescapeFFI
10import Control.Exception (evaluate, throw, try)
10import Control.Exception (evaluate, throw, try)
11import Control.Monad.ST.Unsafe (unsafeIOToST, unsafeSTToIO)
11import Control.Monad.ST.Unsafe (unsafeIOToST, unsafeSTToIO)
12import Data.ByteString as B
12import Data.ByteString as B
13−import Data.ByteString.Internal as B hiding (c2w)
13+import Data.ByteString.Internal as B
14import Data.Text.Encoding.Error (UnicodeException (..))
14import Data.Text.Encoding.Error (UnicodeException (..))
15import Data.Text.Internal (Text (..))
15import Data.Text.Internal (Text (..))
16import Data.Text.Internal.Private (runText)
16import Data.Text.Internal.Private (runText)
pure/Data/Aeson/Parser/UnescapePure.hs+13−36
@@ -119,38 +119,18 @@ decode UtfTail1 point word = case word of
119 w | 0x80 <= w && w <= 0xbf -> (UtfGround, setByte1 point word)
119 w | 0x80 <= w && w <= 0xbf -> (UtfGround, setByte1 point word)
120 _ -> throwDecodeError
120 _ -> throwDecodeError
121
121
122−{-# INLINE decode #-}
123−
124decodeHex :: Word8 -> Word16
122decodeHex :: Word8 -> Word16
125−decodeHex 48 = 0 -- '0'
123+decodeHex x
126−decodeHex 49 = 1 -- '1'
124+ | 48 <= x && x <= 57 = fromIntegral x - 48 -- 0-9
127−decodeHex 50 = 2 -- '2'
125+ | 65 <= x && x <= 70 = fromIntegral x - 55 -- A-F
128−decodeHex 51 = 3 -- '3'
126+ | 97 <= x && x <= 102 = fromIntegral x - 87 -- a-f
129−decodeHex 52 = 4 -- '4'
130−decodeHex 53 = 5 -- '5'
131−decodeHex 54 = 6 -- '6'
132−decodeHex 55 = 7 -- '7'
133−decodeHex 56 = 8 -- '8'
134−decodeHex 57 = 9 -- '9'
135−decodeHex 65 = 10 -- 'A'
136−decodeHex 97 = 10 -- 'a'
137−decodeHex 66 = 11 -- 'B'
138−decodeHex 98 = 11 -- 'b'
139−decodeHex 67 = 12 -- 'C'
140−decodeHex 99 = 12 -- 'c'
141−decodeHex 68 = 13 -- 'D'
142−decodeHex 100 = 13 -- 'd'
143−decodeHex 69 = 14 -- 'E'
144−decodeHex 101 = 14 -- 'e'
145−decodeHex 70 = 15 -- 'F'
146−decodeHex 102 = 15 -- 'f'
147−decodeHex _ = throwDecodeError
127+ | otherwise = throwDecodeError
148−{-# INLINE decodeHex #-}
149
128
150unescapeText' :: ByteString -> Text
129unescapeText' :: ByteString -> Text
151unescapeText' bs = runText $ \done -> do
130unescapeText' bs = runText $ \done -> do
152 dest <- A.new len
131 dest <- A.new len
132+
153− (pos, finalState) <- B.foldl' (f' dest) (return (0, StateNone)) bs
133+ (pos, finalState) <- loop dest (0, StateNone) 0
154
134
155 -- Check final state. Currently pos gets only increased over time, so this check should catch overflows.
135 -- Check final state. Currently pos gets only increased over time, so this check should catch overflows.
156 when ( finalState /= StateNone || pos > len)
136 when ( finalState /= StateNone || pos > len)
@@ -172,11 +152,12 @@ unescapeText' bs = runText $ \done -> do
172 (st', p) ->
152 (st', p) ->
173 return (pos, StateUtf st' p)
153 return (pos, StateUtf st' p)
174
154
175− {-# INLINE runUtf #-}
155+ loop :: A.MArray s -> (Int, State) -> Int -> ST s (Int, State)
176−
156+ loop _ ps i | i >= len = return ps
157+ loop dest ps i = do
158+ let c = B.index bs i -- JP: We can use unsafe index once we prove bounds with Liquid Haskell.
177− f' dest m c = m >>= \s -> f dest s c
159+ ps' <- f dest ps c
178−
160+ loop dest ps' $ i+1
179− {-# INLINE f' #-}
180
161
181 -- No pending state.
162 -- No pending state.
182 f dest (pos, StateNone) c = runUtf dest pos UtfGround 0 c
163 f dest (pos, StateNone) c = runUtf dest pos UtfGround 0 c
@@ -253,8 +234,6 @@ unescapeText' bs = runText $ \done -> do
253 else
234 else
254 writeAndReturn dest pos u StateNone
235 writeAndReturn dest pos u StateNone
255
236
256− {-# INLINE f #-}
257−
258write :: A.MArray s -> Int -> Word16 -> ST s ()
237write :: A.MArray s -> Int -> Word16 -> ST s ()
259write dest pos char =
238write dest pos char =
260 A.unsafeWrite dest pos char
239 A.unsafeWrite dest pos char
@@ -270,8 +249,6 @@ throwDecodeError :: a
270throwDecodeError =
249throwDecodeError =
271 let desc = "Data.Text.Internal.Encoding.decodeUtf8: Invalid UTF-8 stream" in
250 let desc = "Data.Text.Internal.Encoding.decodeUtf8: Invalid UTF-8 stream" in
272 throw (DecodeError desc Nothing)
251 throw (DecodeError desc Nothing)
273−{-# INLINE throwDecodeError #-}
274
252
275unescapeText :: ByteString -> Either UnicodeException Text
253unescapeText :: ByteString -> Either UnicodeException Text
276unescapeText = unsafeDupablePerformIO . try . evaluate . unescapeText'
254unescapeText = unsafeDupablePerformIO . try . evaluate . unescapeText'
277−{-# INLINE unescapeText #-}
run-hlint.shnew file+7
@@ -0,0 +1,7 @@
1+#!/bin/bash
2+
3+if hash hlint 2>/dev/null; then
4+ hlint "$@"
5+else
6+ ./hlint.sh "$@"
7+fi
stack-bench.yaml+2−5
@@ -1,4 +1,4 @@
1−resolver: lts-8.5
1+resolver: nightly-2019-09-21
2# We use aeson in the snapshot to
2# We use aeson in the snapshot to
3# - avoid recompilation of criterion
3# - avoid recompilation of criterion
4# - compare against it
4# - compare against it
@@ -9,7 +9,4 @@ resolver: lts-8.5
9work-dir: .stack-work-bench
9work-dir: .stack-work-bench
10packages:
10packages:
11- benchmarks
11- benchmarks
12−extra-deps:
12+extra-deps: []
13−- aeson-1.2.1.0
14−- quickcheck-instances-0.3.14
15−- th-abstraction-0.2.2.0
stack-ffi-unescape.yamlnew file+9
@@ -0,0 +1,9 @@
1+resolver: nightly-2019-09-21
2+packages:
3+- '.'
4+flags:
5+ aeson:
6+ fast: true
7+ cffi: true
8+extra-deps:
9+- Diff-0.4.0
stack-lts12.yamlnew file+15
@@ -0,0 +1,15 @@
1+resolver: lts-12.26
2+packages:
3+- '.'
4+- attoparsec-iso8601
5+flags:
6+ aeson:
7+ fast: true
8+ cffi: true
9+extra-deps:
10+- base-orphans-0.8.1
11+- hashable-time-0.2.0.1
12+- QuickCheck-2.13.1
13+- quickcheck-instances-0.3.21
14+- splitmix-0.0.2
15+- time-compat-1.9.2.2
stack-lts6.yamldeleted−20
@@ -1,20 +0,0 @@
1−resolver: lts-6.30
2−packages:
3−- '.'
4−- attoparsec-iso8601
5−extra-deps:
6−- integer-logarithms-1
7−- QuickCheck-2.9.2
8−- quickcheck-instances-0.3.14
9−- semigroups-0.18.2
10−- tagged-0.8.5
11−- th-abstraction-0.2.2.0
12−- transformers-compat-0.5.1.4
13−flags:
14−flags:
15− aeson:
16− fast: true
17− attoparsec-iso8601:
18− fast: true
19− semigroups:
20− bytestring-builder: false
stack-lts7.yamldeleted−13
@@ -1,13 +0,0 @@
1−resolver: lts-7.19
2−packages:
3−- '.'
4−- attoparsec-iso8601
5−extra-deps:
6−- integer-logarithms-1
7−- quickcheck-instances-0.3.14
8−- th-abstraction-0.2.2.0
9−flags:
10− aeson:
11− fast: true
12− attoparsec-iso8601:
13− fast: true
stack-lts8.yamldeleted−12
@@ -1,12 +0,0 @@
1−resolver: lts-8.5
2−packages:
3−- '.'
4−- attoparsec-iso8601
5−extra-deps:
6−- quickcheck-instances-0.3.14
7−- th-abstraction-0.2.2.0
8−flags:
9− aeson:
10− fast: true
11− attoparsec-iso8601:
12− fast: true
stack-lts9.yamldeleted−12
@@ -1,12 +0,0 @@
1−resolver: lts-9.3
2−packages:
3−- '.'
4−- attoparsec-iso8601
5−extra-deps:
6−- quickcheck-instances-0.3.16
7−- QuickCheck-2.10.0.1
8−flags:
9− aeson:
10− fast: true
11− attoparsec-iso8601:
12− fast: true
stack-nightly.yaml+3−1
@@ -1,4 +1,4 @@
1−resolver: nightly-2017-09-04
1+resolver: nightly-2019-10-14
2packages:
2packages:
3- '.'
3- '.'
4- attoparsec-iso8601
4- attoparsec-iso8601
@@ -7,3 +7,5 @@ flags:
7 fast: true
7 fast: true
8 attoparsec-iso8601:
8 attoparsec-iso8601:
9 fast: true
9 fast: true
10+extra-deps:
11+- generic-deriving-1.12.4
stack-pure-unescape.yamldeleted−10
@@ -1,10 +0,0 @@
1−resolver: lts-8.1
2−packages:
3−- '.'
4−extra-deps:
5−- quickcheck-instances-0.3.14
6−- th-abstraction-0.2.2.0
7−flags:
8− aeson:
9− fast: true
10− cffi: false
tests/DataFamilies/Encoders.hs+1−1
@@ -1,10 +1,10 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE GADTs #-}
2{-# LANGUAGE GADTs #-}
3+{-# LANGUAGE NoImplicitPrelude #-}
3{-# LANGUAGE TemplateHaskell #-}
4{-# LANGUAGE TemplateHaskell #-}
4
5
5module DataFamilies.Encoders (module DataFamilies.Encoders) where
6module DataFamilies.Encoders (module DataFamilies.Encoders) where
6
7
7−import Prelude ()
8import Prelude.Compat
8import Prelude.Compat
9
9
10import Data.Aeson.TH
10import Data.Aeson.TH
tests/DataFamilies/Instances.hs+2−1
@@ -1,12 +1,13 @@
1{-# LANGUAGE FlexibleInstances #-}
1{-# LANGUAGE FlexibleInstances #-}
2{-# LANGUAGE GADTs #-}
2{-# LANGUAGE GADTs #-}
3+{-# LANGUAGE NoImplicitPrelude #-}
3{-# LANGUAGE TemplateHaskell #-}
4{-# LANGUAGE TemplateHaskell #-}
4{-# LANGUAGE TypeFamilies #-}
5{-# LANGUAGE TypeFamilies #-}
6+
5{-# OPTIONS_GHC -fno-warn-orphans #-}
7{-# OPTIONS_GHC -fno-warn-orphans #-}
6
8
7module DataFamilies.Instances () where
9module DataFamilies.Instances () where
8
10
9−import Prelude ()
10import Prelude.Compat
11import Prelude.Compat
11
12
12import Data.Aeson.TH
13import Data.Aeson.TH
tests/DataFamilies/Properties.hs+6−5
@@ -1,20 +1,21 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
2
3
3module DataFamilies.Properties (tests) where
4module DataFamilies.Properties (tests) where
4
5
5−import Prelude ()
6import Prelude.Compat
6import Prelude.Compat
7
7
8import DataFamilies.Encoders
8import DataFamilies.Encoders
9import DataFamilies.Instances ()
9import DataFamilies.Instances ()
10−import Properties hiding (tests)
10+import PropUtils
11
11
12+
12−import Test.Framework (Test, testGroup)
13+import Test.Tasty (TestTree, testGroup)
13−import Test.Framework.Providers.QuickCheck2 (testProperty)
14+import Test.Tasty.QuickCheck (testProperty)
14
15
15--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
16
17
17−tests :: Test
18+tests :: TestTree
18tests = testGroup "data families" [
19tests = testGroup "data families" [
19 testGroup "template-haskell" [
20 testGroup "template-haskell" [
20 testGroup "toJSON" [
21 testGroup "toJSON" [
tests/DataFamilies/Types.hs+1−1
@@ -4,13 +4,13 @@
4{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GADTs #-}
5{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+{-# LANGUAGE NoImplicitPrelude #-}
7{-# LANGUAGE StandaloneDeriving #-}
8{-# LANGUAGE StandaloneDeriving #-}
8{-# LANGUAGE TemplateHaskell #-}
9{-# LANGUAGE TemplateHaskell #-}
9{-# LANGUAGE TypeFamilies #-}
10{-# LANGUAGE TypeFamilies #-}
10
11
11module DataFamilies.Types (module DataFamilies.Types) where
12module DataFamilies.Types (module DataFamilies.Types) where
12
13
13−import Prelude ()
14import Prelude.Compat
14import Prelude.Compat
15
15
16import Generics.Deriving.TH (deriveAll0)
16import Generics.Deriving.TH (deriveAll0)
tests/Encoders.hs+76−7
@@ -1,11 +1,12 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE GADTs #-}
2{-# LANGUAGE GADTs #-}
3+{-# LANGUAGE NoImplicitPrelude #-}
3{-# LANGUAGE TemplateHaskell #-}
4{-# LANGUAGE TemplateHaskell #-}
4
5
5module Encoders (module Encoders) where
6module Encoders (module Encoders) where
6
7
7−import Prelude ()
8import Prelude.Compat
8import Prelude.Compat
9+import Data.Text (Text)
9
10
10import Data.Aeson.TH
11import Data.Aeson.TH
11import Data.Aeson.Types
12import Data.Aeson.Types
@@ -96,6 +97,18 @@ gNullaryToEncodingObjectWithSingleField = genericToEncoding optsObjectWithSingle
96gNullaryParseJSONObjectWithSingleField :: Value -> Parser Nullary
97gNullaryParseJSONObjectWithSingleField :: Value -> Parser Nullary
97gNullaryParseJSONObjectWithSingleField = genericParseJSON optsObjectWithSingleField
98gNullaryParseJSONObjectWithSingleField = genericParseJSON optsObjectWithSingleField
98
99
100+keyOptions :: JSONKeyOptions
101+keyOptions = defaultJSONKeyOptions { keyModifier = ('k' :) }
102+
103+gNullaryToJSONKey :: Nullary -> Either String Text
104+gNullaryToJSONKey x = case genericToJSONKey keyOptions of
105+ ToJSONKeyText p _ -> Right (p x)
106+ _ -> Left "Should be a ToJSONKeyText"
107+
108+gNullaryFromJSONKey :: Text -> Parser Nullary
109+gNullaryFromJSONKey t = case genericFromJSONKey keyOptions of
110+ FromJSONKeyTextParser p -> p t
111+ _ -> fail "Not a TextParser"
99
112
100--------------------------------------------------------------------------------
113--------------------------------------------------------------------------------
101-- SomeType encoders/decoders
114-- SomeType encoders/decoders
@@ -175,7 +188,6 @@ gSomeTypeToEncoding2ElemArray = genericToEncoding opts2ElemArray
175gSomeTypeParseJSON2ElemArray :: Value -> Parser (SomeType Int)
188gSomeTypeParseJSON2ElemArray :: Value -> Parser (SomeType Int)
176gSomeTypeParseJSON2ElemArray = genericParseJSON opts2ElemArray
189gSomeTypeParseJSON2ElemArray = genericParseJSON opts2ElemArray
177
190
178−#if __GLASGOW_HASKELL__ >= 706
179gSomeTypeLiftToEncoding2ElemArray :: LiftToEncoding SomeType a
191gSomeTypeLiftToEncoding2ElemArray :: LiftToEncoding SomeType a
180gSomeTypeLiftToEncoding2ElemArray = genericLiftToEncoding opts2ElemArray
192gSomeTypeLiftToEncoding2ElemArray = genericLiftToEncoding opts2ElemArray
181
193
@@ -184,7 +196,6 @@ gSomeTypeLiftToJSON2ElemArray = genericLiftToJSON opts2ElemArray
184
196
185gSomeTypeLiftParseJSON2ElemArray :: LiftParseJSON SomeType a
197gSomeTypeLiftParseJSON2ElemArray :: LiftParseJSON SomeType a
186gSomeTypeLiftParseJSON2ElemArray = genericLiftParseJSON opts2ElemArray
198gSomeTypeLiftParseJSON2ElemArray = genericLiftParseJSON opts2ElemArray
187−#endif
188
199
189
200
190gSomeTypeToJSONTaggedObject :: SomeType Int -> Value
201gSomeTypeToJSONTaggedObject :: SomeType Int -> Value
@@ -196,7 +207,6 @@ gSomeTypeToEncodingTaggedObject = genericToEncoding optsTaggedObject
196gSomeTypeParseJSONTaggedObject :: Value -> Parser (SomeType Int)
207gSomeTypeParseJSONTaggedObject :: Value -> Parser (SomeType Int)
197gSomeTypeParseJSONTaggedObject = genericParseJSON optsTaggedObject
208gSomeTypeParseJSONTaggedObject = genericParseJSON optsTaggedObject
198
209
199−#if __GLASGOW_HASKELL__ >= 706
200gSomeTypeLiftToEncodingTaggedObject :: LiftToEncoding SomeType a
210gSomeTypeLiftToEncodingTaggedObject :: LiftToEncoding SomeType a
201gSomeTypeLiftToEncodingTaggedObject = genericLiftToEncoding optsTaggedObject
211gSomeTypeLiftToEncodingTaggedObject = genericLiftToEncoding optsTaggedObject
202
212
@@ -205,7 +215,6 @@ gSomeTypeLiftToJSONTaggedObject = genericLiftToJSON optsTaggedObject
205
215
206gSomeTypeLiftParseJSONTaggedObject :: LiftParseJSON SomeType a
216gSomeTypeLiftParseJSONTaggedObject :: LiftParseJSON SomeType a
207gSomeTypeLiftParseJSONTaggedObject = genericLiftParseJSON optsTaggedObject
217gSomeTypeLiftParseJSONTaggedObject = genericLiftParseJSON optsTaggedObject
208−#endif
209
218
210
219
211gSomeTypeToJSONObjectWithSingleField :: SomeType Int -> Value
220gSomeTypeToJSONObjectWithSingleField :: SomeType Int -> Value
@@ -217,7 +226,6 @@ gSomeTypeToEncodingObjectWithSingleField = genericToEncoding optsObjectWithSingl
217gSomeTypeParseJSONObjectWithSingleField :: Value -> Parser (SomeType Int)
226gSomeTypeParseJSONObjectWithSingleField :: Value -> Parser (SomeType Int)
218gSomeTypeParseJSONObjectWithSingleField = genericParseJSON optsObjectWithSingleField
227gSomeTypeParseJSONObjectWithSingleField = genericParseJSON optsObjectWithSingleField
219
228
220−#if __GLASGOW_HASKELL__ >= 706
221gSomeTypeLiftToEncodingObjectWithSingleField :: LiftToEncoding SomeType a
229gSomeTypeLiftToEncodingObjectWithSingleField :: LiftToEncoding SomeType a
222gSomeTypeLiftToEncodingObjectWithSingleField = genericLiftToEncoding optsObjectWithSingleField
230gSomeTypeLiftToEncodingObjectWithSingleField = genericLiftToEncoding optsObjectWithSingleField
223
231
@@ -226,7 +234,6 @@ gSomeTypeLiftToJSONObjectWithSingleField = genericLiftToJSON optsObjectWithSingl
226
234
227gSomeTypeLiftParseJSONObjectWithSingleField :: LiftParseJSON SomeType a
235gSomeTypeLiftParseJSONObjectWithSingleField :: LiftParseJSON SomeType a
228gSomeTypeLiftParseJSONObjectWithSingleField = genericLiftParseJSON optsObjectWithSingleField
236gSomeTypeLiftParseJSONObjectWithSingleField = genericLiftParseJSON optsObjectWithSingleField
229−#endif
230
237
231
238
232gSomeTypeToJSONOmitNothingFields :: SomeType Int -> Value
239gSomeTypeToJSONOmitNothingFields :: SomeType Int -> Value
@@ -235,6 +242,58 @@ gSomeTypeToJSONOmitNothingFields = genericToJSON optsOmitNothingFields
235gSomeTypeToEncodingOmitNothingFields :: SomeType Int -> Encoding
242gSomeTypeToEncodingOmitNothingFields :: SomeType Int -> Encoding
236gSomeTypeToEncodingOmitNothingFields = genericToEncoding optsOmitNothingFields
243gSomeTypeToEncodingOmitNothingFields = genericToEncoding optsOmitNothingFields
237
244
245+
246+thSomeTypeParseJSONRejectUnknownFields :: Value -> Parser (SomeType Int)
247+thSomeTypeParseJSONRejectUnknownFields = $(mkParseJSON optsRejectUnknownFields ''SomeType)
248+
249+gSomeTypeParseJSONRejectUnknownFields :: Value -> Parser (SomeType Int)
250+gSomeTypeParseJSONRejectUnknownFields = genericParseJSON optsRejectUnknownFields
251+
252+
253+--------------------------------------------------------------------------------
254+-- Foo decoders
255+--------------------------------------------------------------------------------
256+
257+thFooParseJSONRejectUnknownFields :: Value -> Parser Foo
258+thFooParseJSONRejectUnknownFields = $(mkParseJSON optsRejectUnknownFields ''Foo)
259+
260+gFooParseJSONRejectUnknownFields :: Value -> Parser Foo
261+gFooParseJSONRejectUnknownFields = genericParseJSON optsRejectUnknownFields
262+
263+
264+thFooParseJSONRejectUnknownFieldsTagged :: Value -> Parser Foo
265+thFooParseJSONRejectUnknownFieldsTagged = $(mkParseJSON optsRejectUnknownFieldsTagged ''Foo)
266+
267+gFooParseJSONRejectUnknownFieldsTagged :: Value -> Parser Foo
268+gFooParseJSONRejectUnknownFieldsTagged = genericParseJSON optsRejectUnknownFieldsTagged
269+
270+
271+--------------------------------------------------------------------------------
272+-- Option fields
273+--------------------------------------------------------------------------------
274+
275+thOptionFieldToJSON :: OptionField -> Value
276+thOptionFieldToJSON = $(mkToJSON optsOptionField 'OptionField)
277+
278+thOptionFieldToEncoding :: OptionField -> Encoding
279+thOptionFieldToEncoding = $(mkToEncoding optsOptionField 'OptionField)
280+
281+thOptionFieldParseJSON :: Value -> Parser OptionField
282+thOptionFieldParseJSON = $(mkParseJSON optsOptionField 'OptionField)
283+
284+gOptionFieldToJSON :: OptionField -> Value
285+gOptionFieldToJSON = genericToJSON optsOptionField
286+
287+gOptionFieldToEncoding :: OptionField -> Encoding
288+gOptionFieldToEncoding = genericToEncoding optsOptionField
289+
290+gOptionFieldParseJSON :: Value -> Parser OptionField
291+gOptionFieldParseJSON = genericParseJSON optsOptionField
292+
293+thMaybeFieldToJSON :: MaybeField -> Value
294+thMaybeFieldToJSON = $(mkToJSON optsOptionField 'MaybeField)
295+
296+
238--------------------------------------------------------------------------------
297--------------------------------------------------------------------------------
239-- IncoherentInstancesNeeded
298-- IncoherentInstancesNeeded
240--------------------------------------------------------------------------------
299--------------------------------------------------------------------------------
@@ -387,3 +446,13 @@ gOneConstructorToEncodingTagged = genericToEncoding optsTagSingleConstructors
387
446
388gOneConstructorParseJSONTagged :: Value -> Parser OneConstructor
447gOneConstructorParseJSONTagged :: Value -> Parser OneConstructor
389gOneConstructorParseJSONTagged = genericParseJSON optsTagSingleConstructors
448gOneConstructorParseJSONTagged = genericParseJSON optsTagSingleConstructors
449+
450+--------------------------------------------------------------------------------
451+-- Product2 encoders/decoders
452+--------------------------------------------------------------------------------
453+
454+thProduct2ParseJSON :: Value -> Parser (Product2 Int Bool)
455+thProduct2ParseJSON = $(mkParseJSON defaultOptions ''Product2)
456+
457+gProduct2ParseJSON :: Value -> Parser (Product2 Int Bool)
458+gProduct2ParseJSON = genericParseJSON defaultOptions
tests/ErrorMessages.hs+207−54
@@ -1,3 +1,4 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE ScopedTypeVariables #-}
3
4
@@ -6,66 +7,218 @@ module ErrorMessages
6 tests
7 tests
7 ) where
8 ) where
8
9
9−import Prelude ()
10import Prelude.Compat
10import Prelude.Compat
11
11
12−import Data.Aeson (FromJSON(..), eitherDecode)
12+import Data.Aeson (FromJSON(..), Value, json)
13+import Data.Aeson.Types (Parser)
14+import Data.Aeson.Parser (eitherDecodeWith)
15+import Data.Aeson.Internal (formatError, iparse)
16+import Data.Algorithm.Diff (PolyDiff (..), getGroupedDiff)
13import Data.Proxy (Proxy(..))
17import Data.Proxy (Proxy(..))
18+import Data.Semigroup ((<>))
19+import Data.Sequence (Seq)
14import Instances ()
20import Instances ()
15import Numeric.Natural (Natural)
21import Numeric.Natural (Natural)
16−import Test.Framework (Test)
22+import Test.Tasty (TestTree, TestName)
17−import Test.Framework.Providers.HUnit (testCase)
23+import Test.Tasty.Golden.Advanced (goldenTest)
18−import Test.HUnit (Assertion, assertFailure, assertEqual)
19import qualified Data.ByteString.Lazy.Char8 as L
24import qualified Data.ByteString.Lazy.Char8 as L
20import qualified Data.HashMap.Strict as HM
25import qualified Data.HashMap.Strict as HM
21
26
27+import Encoders
28+import Types
29+
22−tests :: [Test]
30+tests :: [TestTree]
23tests =
31tests =
24− [
32+ [ aesonGoldenTest "simple" "tests/golden/simple.expected" output
25− testCase "Int" int
33+ , aesonGoldenTest "generic" "tests/golden/generic.expected" (outputGeneric G)
26− , testCase "Integer" integer
27− , testCase "Natural" natural
28− , testCase "String" string
29− , testCase "HashMap" hashMap
30− ]
34+ ]
31−
35+
32−int :: Assertion
36+output :: Output
33−int = do
37+output = concat
34− let t = test (Proxy :: Proxy Int)
38+ [ testFor "Int" (Proxy :: Proxy Int)
35− t "\"\"" $ expected "Int" "String"
39+ [ "\"\""
36− t "[]" $ expected "Int" "Array"
40+ , "[]"
37− t "{}" $ expected "Int" "Object"
41+ , "{}"
38− t "null" $ expected "Int" "Null"
42+ , "null"
39−
43+ ]
40−integer :: Assertion
44+
41−integer = do
42− let t = test (Proxy :: Proxy Integer)
45+ , testFor "Integer" (Proxy :: Proxy Integer)
43− t "44.44" $ expected "Integer" "floating number 44.44"
46+ [ "44.44"
44−
47+ ]
45−natural :: Assertion
48+
46−natural = do
47− let t = test (Proxy :: Proxy Natural)
49+ , testFor "Natural" (Proxy :: Proxy Natural)
48− t "44.44" $ expected "Natural" "floating number 44.44"
50+ [ "44.44"
49− t "-50" $ expected "Natural" "negative number -50"
51+ , "-50"
50−
52+ ]
51−string :: Assertion
53+
52−string = do
53− let t = test (Proxy :: Proxy String)
54+ , testFor "String" (Proxy :: Proxy String)
54− t "1" $ expected "String" "Number"
55+ [ "1"
55− t "[]" $ expected "String" "Array"
56+ , "[]"
56− t "{}" $ expected "String" "Object"
57+ , "{}"
57− t "null" $ expected "String" "Null"
58+ , "null"
58−
59+ ]
59−hashMap :: Assertion
60+
60−hashMap = do
61− let t = test (Proxy :: Proxy (HM.HashMap String Int))
61+ , testFor "HashMap" (Proxy :: Proxy (HM.HashMap String Int))
62− t "\"\"" $ expected "HashMap k v" "String"
62+ [ "\"\""
63− t "[]" $ expected "HashMap k v" "Array"
63+ , "[]"
64−
64+ ]
65−expected :: String -> String -> String
65+
66−expected ex enc = "Error in $: expected " ++ ex ++ ", encountered " ++ enc
66+ -- issue #356
67−
67+ , testFor "Either" (Proxy :: Proxy (Int, Either (Int, Bool) ()))
68−test :: forall a proxy . (FromJSON a, Show a) => proxy a -> L.ByteString -> String -> Assertion
68+ [ "[1,{\"Left\":[2,3]}]"
69−test _ v msg = case eitherDecode v of
69+ ]
70− Left e -> assertEqual "Invalid error message" msg e
70+
71− Right (x :: a) -> assertFailure $ "Expected parsing to fail but it suceeded with: " ++ show x
71+ -- issue #358
72+ , testFor "Seq" (Proxy :: Proxy (Seq Int))
73+ [ "[0,1,true]"
74+ ]
75+ ]
76+
77+data Choice = TH | G
78+
79+outputGeneric :: Choice -> Output
80+outputGeneric choice = concat
81+ [ testWith "OneConstructor"
82+ (select
83+ thOneConstructorParseJSONDefault
84+ gOneConstructorParseJSONDefault)
85+ [ "\"X\""
86+ , "[0]"
87+ ]
88+
89+ , testWith "Nullary"
90+ (select
91+ thNullaryParseJSONString
92+ gNullaryParseJSONString)
93+ [ "\"X\""
94+ , "[]"
95+ ]
96+
97+ , testWithSomeType "SomeType (tagged)"
98+ (select
99+ thSomeTypeParseJSONTaggedObject
100+ gSomeTypeParseJSONTaggedObject)
101+ [ "{\"tag\": \"unary\", \"contents\": true}"
102+ , "{\"tag\": \"unary\"}"
103+ , "{\"tag\": \"record\"}"
104+ , "{\"tag\": \"record\", \"testone\": true, \"testtwo\": null, \"testthree\": null}"
105+ , "{\"tag\": \"X\"}"
106+ , "{}"
107+ , "[]"
108+ ]
109+
110+ , testWithSomeType "SomeType (single-field)"
111+ (select
112+ thSomeTypeParseJSONObjectWithSingleField
113+ gSomeTypeParseJSONObjectWithSingleField)
114+ [ "{\"unary\": {}}"
115+ , "{\"unary\": []}"
116+ , "{\"X\": []}"
117+ , "{\"record\": {}, \"W\":{}}"
118+ , "{}"
119+ , "[]"
120+ , "{\"unary\""
121+ , "{\"unary\":"
122+ , "{\"unary\":1"
123+ ]
124+
125+ , testWithSomeType "SomeType (two-element array)"
126+ (select
127+ thSomeTypeParseJSON2ElemArray
128+ gSomeTypeParseJSON2ElemArray)
129+ [ "[\"unary\", true]"
130+ , "[\"record\", null]"
131+ , "[\"X\", 0]"
132+ , "[null, 0]"
133+ , "[]"
134+ , "{}"
135+ , "[1"
136+ , "[1,"
137+ ]
138+
139+ , testWithSomeType "SomeType (reject unknown fields)"
140+ (select
141+ thSomeTypeParseJSONRejectUnknownFields
142+ gSomeTypeParseJSONRejectUnknownFields)
143+ [ "{\"tag\": \"record\", \"testOne\": 1.0, \"testZero\": 1}"
144+ , "{\"testZero\": 1}"
145+ , "{\"tag\": \"record\", \"testone\": true, \"testtwo\": null, \"testthree\": null}"
146+ ]
147+
148+ , testWithFoo "Foo (reject unknown fields)"
149+ (select
150+ thFooParseJSONRejectUnknownFields
151+ gFooParseJSONRejectUnknownFields)
152+ [ "{\"tag\": \"foo\"}"
153+ ]
154+
155+ , testWithFoo "Foo (reject unknown fields, tagged single)"
156+ (select
157+ thFooParseJSONRejectUnknownFieldsTagged
158+ gFooParseJSONRejectUnknownFieldsTagged)
159+ [ "{\"tag\": \"foo\", \"unknownField\": 0}"
160+ ]
161+
162+ , testWith "EitherTextInt"
163+ (select
164+ thEitherTextIntParseJSONUntaggedValue
165+ gEitherTextIntParseJSONUntaggedValue)
166+ [ "\"X\""
167+ , "[]"
168+ ]
169+
170+ , testWith "Product2 Int Bool"
171+ (select
172+ thProduct2ParseJSON
173+ gProduct2ParseJSON)
174+ [ "[1, null]"
175+ , "[]"
176+ , "{}"
177+ ]
178+ ]
179+ where
180+ select a b = case choice of
181+ TH -> a
182+ G -> b
183+
184+-- Test infrastructure
185+
186+type Output = [String]
187+
188+outputLine :: String -> Output
189+outputLine = pure
190+
191+aesonGoldenTest :: TestName -> FilePath -> Output -> TestTree
192+aesonGoldenTest name ref out = goldenTest name (L.readFile ref) act cmp upd
193+ where
194+ act = pure (L.pack (unlines out))
195+ upd = L.writeFile ref
196+ cmp x y | x == y = return Nothing
197+ cmp x y = return $ Just $ unlines $
198+ concatMap f (getGroupedDiff (L.lines x) (L.lines y))
199+ where
200+ f (First xs) = map (cons3 '-' . L.unpack) xs
201+ f (Second ys) = map (cons3 '+' . L.unpack) ys
202+ -- we print unchanged lines too. It shouldn't be a problem while we have
203+ -- reasonably small examples
204+ f (Both xs _) = map (cons3 ' ' . L.unpack) xs
205+ -- we add three characters, so the changed lines are easier to spot
206+ cons3 c cs = c : c : c : ' ' : cs
207+
208+testWith :: Show a => String -> (Value -> Parser a) -> [L.ByteString] -> Output
209+testWith name parser ts =
210+ outputLine name <>
211+ foldMap (\s ->
212+ case eitherDecodeWith json (iparse parser) s of
213+ Left err -> outputLine $ uncurry formatError err
214+ Right a -> outputLine $ show a) ts
215+
216+testFor :: forall a proxy. (FromJSON a, Show a)
217+ => String -> proxy a -> [L.ByteString] -> Output
218+testFor name _ = testWith name (parseJSON :: Value -> Parser a)
219+
220+testWithSomeType :: String -> (Value -> Parser (SomeType Int)) -> [L.ByteString] -> Output
221+testWithSomeType = testWith
222+
223+testWithFoo :: String -> (Value -> Parser Foo) -> [L.ByteString] -> Output
224+testWithFoo = testWith
tests/Functions.hs+2−1
@@ -1,10 +1,11 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
1module Functions
3module Functions
2 (
4 (
3 approxEq
5 approxEq
4 , approxEqWith
6 , approxEqWith
5 ) where
7 ) where
6
8
7−import Prelude ()
8import Prelude.Compat
9import Prelude.Compat
9
10
10approxEq :: (Fractional a, Ord a) => a -> a -> Bool
11approxEq :: (Fractional a, Ord a) => a -> a -> Bool
tests/Instances.hs+7−62
@@ -1,13 +1,13 @@
1−{-# LANGUAGE CPP #-}
2{-# LANGUAGE FlexibleContexts #-}
1{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleInstances #-}
2{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE NoImplicitPrelude #-}
4{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE RecordWildCards #-}
5{-# LANGUAGE RecordWildCards #-}
6+
6{-# OPTIONS_GHC -fno-warn-orphans #-}
7{-# OPTIONS_GHC -fno-warn-orphans #-}
7
8
8module Instances () where
9module Instances () where
9
10
10−import Prelude ()
11import Prelude.Compat
11import Prelude.Compat
12
12
13import Control.Applicative (empty)
13import Control.Applicative (empty)
@@ -22,35 +22,14 @@ import Types
22import qualified Data.DList as DList
22import qualified Data.DList as DList
23import qualified Data.HashMap.Strict as HM
23import qualified Data.HashMap.Strict as HM
24
24
25−#if !MIN_VERSION_QuickCheck(2,9,0)
26−import Control.Applicative (Const(..))
27−import Data.Functor.Identity (Identity (..))
28−import Data.List.NonEmpty (NonEmpty(..))
29−import Data.Version
30−import Test.QuickCheck (getNonNegative, listOf1, resize)
31−#endif
32−
33−#if !MIN_VERSION_QuickCheck(2,10,0)
34−import Data.Functor.Compose (Compose (..))
35−import Data.Proxy (Proxy(..))
36−#endif
37−
38import Data.Orphans ()
25import Data.Orphans ()
39import Test.QuickCheck.Instances ()
26import Test.QuickCheck.Instances ()
40−#if MIN_VERSION_base(4,7,0)
41import Data.Hashable.Time ()
27import Data.Hashable.Time ()
42−#endif
43−
44−#if !MIN_VERSION_base(4,8,0) && !MIN_VERSION_QuickCheck(2,8,3)
45−import Numeric.Natural
46−#endif
47−
48−{-# ANN module ("HLint: ignore Use fewer imports"::String) #-}
49
28
50-- "System" types.
29-- "System" types.
51
30
52instance Arbitrary DotNetTime where
31instance Arbitrary DotNetTime where
53− arbitrary = DotNetTime `liftM` arbitrary
32+ arbitrary = DotNetTime `fmap` arbitrary
54 shrink = map DotNetTime . shrink . fromDotNetTime
33 shrink = map DotNetTime . shrink . fromDotNetTime
55
34
56-- | Compare timezone part only on 'timeZoneMinutes'
35-- | Compare timezone part only on 'timeZoneMinutes'
@@ -174,49 +153,15 @@ instance Arbitrary EitherTextInt where
174instance Arbitrary (GADT String) where
153instance Arbitrary (GADT String) where
175 arbitrary = GADT <$> arbitrary
154 arbitrary = GADT <$> arbitrary
176
155
156+instance Arbitrary OptionField where
157+ arbitrary = OptionField <$> arbitrary
158+
159+
177instance ApproxEq Char where
160instance ApproxEq Char where
178 (=~) = (==)
161 (=~) = (==)
179
162
180instance (ApproxEq a) => ApproxEq [a] where
163instance (ApproxEq a) => ApproxEq [a] where
181 a =~ b = length a == length b && all (uncurry (=~)) (zip a b)
164 a =~ b = length a == length b && all (uncurry (=~)) (zip a b)
182
165
183−-- Version tags are deprecated, so we avoid using them in the Arbitrary
184−-- instance. However, the recommended constructor 'makeVersion' is not
185−-- exported by "Data.Version" until base-4.8.0.0. For previous versions,
186−-- a definition is given below.
187−
188−
189−#if !MIN_VERSION_base(4,8,0) && !MIN_VERSION_QuickCheck(2,8,3)
190−instance Arbitrary Natural where
191− arbitrary = fromInteger . abs <$> arbitrary
192−#endif
193−
194−#if !MIN_VERSION_QuickCheck(2,10,0)
195−instance Arbitrary (Proxy a) where
196− arbitrary = pure Proxy
197−
198−instance Arbitrary (f (g a)) => Arbitrary (Compose f g a) where
199− arbitrary = Compose <$> arbitrary
200−#endif
201−
202instance Arbitrary a => Arbitrary (DList.DList a) where
166instance Arbitrary a => Arbitrary (DList.DList a) where
203 arbitrary = DList.fromList <$> arbitrary
167 arbitrary = DList.fromList <$> arbitrary
204−
205−#if !MIN_VERSION_QuickCheck(2,9,0)
206−instance Arbitrary a => Arbitrary (Const a b) where
207− arbitrary = Const <$> arbitrary
208−
209−instance Arbitrary a => Arbitrary (NonEmpty a) where
210− arbitrary = (:|) <$> arbitrary <*> arbitrary
211−
212−instance Arbitrary Version where
213− arbitrary = makeVersion . fmap getNonNegative <$> resize 4 (listOf1 arbitrary)
214−
215−#if !MIN_VERSION_base(4,8,0)
216−makeVersion :: [Int] -> Version
217−makeVersion b = Version b []
218−#endif
219−
220−instance Arbitrary a => Arbitrary (Identity a) where
221− arbitrary = Identity <$> arbitrary
222−#endif
tests/JSONTestSuite/parsers/test_haskell-aeson/testaeson.cabal+5−3
@@ -1,6 +1,8 @@
1−-- This file has been generated from package.yaml by hpack version 0.14.0.
1+-- This file has been generated from package.yaml by hpack version 0.28.2.
2--
2--
3-- see: https://github.com/sol/hpack
3-- see: https://github.com/sol/hpack
4+--
5+-- hash: 716ef4c3e64849526f097b85ed05015e0072c2e2afcc1f91e58d8bea68b123b2
4
6
5name: testaeson
7name: testaeson
6version: 0.0.0
8version: 0.0.0
@@ -12,8 +14,8 @@ executable testaeson
12 hs-source-dirs:
14 hs-source-dirs:
13 src
15 src
14 build-depends:
16 build-depends:
17+ aeson
15− base >= 4.7 && < 5
18+ , base >=4.7 && <5
16− , aeson
17 , base-prelude
19 , base-prelude
18 , bytestring
20 , bytestring
19 default-language: Haskell2010
21 default-language: Haskell2010
tests/Options.hs+19−1
@@ -1,6 +1,7 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
1module Options (module Options) where
3module Options (module Options) where
2
4
3−import Prelude ()
4import Prelude.Compat
5import Prelude.Compat
5
6
6import Data.Aeson.Types
7import Data.Aeson.Types
@@ -48,3 +49,20 @@ optsTagSingleConstructors = optsDefault
48 { tagSingleConstructors = True
49 { tagSingleConstructors = True
49 , allNullaryToStringTag = False
50 , allNullaryToStringTag = False
50 }
51 }
52+
53+optsOptionField :: Options
54+optsOptionField = optsDefault
55+ { fieldLabelModifier = const "field"
56+ , omitNothingFields = True
57+ }
58+
59+optsRejectUnknownFields :: Options
60+optsRejectUnknownFields = optsDefault
61+ { rejectUnknownFields = True
62+ }
63+
64+optsRejectUnknownFieldsTagged :: Options
65+optsRejectUnknownFieldsTagged = optsDefault
66+ { rejectUnknownFields = True
67+ , tagSingleConstructors = True
68+ }
tests/PropUtils.hsnew file+208
@@ -0,0 +1,208 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE RankNTypes #-}
4+
5+module PropUtils (module PropUtils) where
6+
7+import Prelude.Compat
8+
9+import Data.Aeson (eitherDecode, encode)
10+import Data.Aeson.Encoding (encodingToLazyByteString)
11+import Data.Aeson.Internal (IResult(..), formatError, ifromJSON, iparse)
12+import qualified Data.Aeson.Internal as I
13+import Data.Aeson.Parser (value)
14+import Data.Aeson.Types
15+import Data.HashMap.Strict (HashMap)
16+import Data.Hashable (Hashable)
17+import Data.Int (Int8)
18+import Data.Map (Map)
19+import Data.Time (ZonedTime)
20+import Encoders
21+import Instances ()
22+import Test.QuickCheck (Arbitrary(..), Property, Testable, (===), (.&&.), counterexample)
23+import Types
24+import qualified Data.Attoparsec.Lazy as L
25+import qualified Data.ByteString.Lazy.Char8 as L
26+import qualified Data.HashMap.Strict as H
27+import qualified Data.Map as Map
28+import qualified Data.Text as T
29+import qualified Data.Vector as V
30+
31+
32+encodeDouble :: Double -> Double -> Property
33+encodeDouble num denom
34+ | isInfinite d || isNaN d = encode d === "null"
35+ | otherwise = (read . L.unpack . encode) d === d
36+ where d = num / denom
37+
38+encodeInteger :: Integer -> Property
39+encodeInteger i = encode i === L.pack (show i)
40+
41+toParseJSON :: (Eq a, Show a) =>
42+ (Value -> Parser a) -> (a -> Value) -> a -> Property
43+toParseJSON parsejson tojson x =
44+ case iparse parsejson . tojson $ x of
45+ IError path msg -> failure "parse" (formatError path msg) x
46+ ISuccess x' -> x === x'
47+
48+toParseJSON1
49+ :: (Eq (f Int), Show (f Int))
50+ => (forall a. LiftParseJSON f a)
51+ -> (forall a. LiftToJSON f a)
52+ -> f Int
53+ -> Property
54+toParseJSON1 parsejson1 tojson1 = toParseJSON parsejson tojson
55+ where
56+ parsejson = parsejson1 parseJSON (listParser parseJSON)
57+ tojson = tojson1 toJSON (listValue toJSON)
58+
59+roundTripEnc :: (FromJSON a, ToJSON a, Show a) =>
60+ (a -> a -> Property) -> a -> a -> Property
61+roundTripEnc eq _ i =
62+ case fmap ifromJSON . L.parse value . encode $ i of
63+ L.Done _ (ISuccess v) -> v `eq` i
64+ L.Done _ (IError path err) -> failure "fromJSON" (formatError path err) i
65+ L.Fail _ _ err -> failure "parse" err i
66+
67+roundTripNoEnc :: (FromJSON a, ToJSON a, Show a) =>
68+ (a -> a -> Property) -> a -> a -> Property
69+roundTripNoEnc eq _ i =
70+ case ifromJSON . toJSON $ i of
71+ (ISuccess v) -> v `eq` i
72+ (IError path err) -> failure "fromJSON" (formatError path err) i
73+
74+roundTripEq :: (Eq a, FromJSON a, ToJSON a, Show a) => a -> a -> Property
75+roundTripEq x y = roundTripEnc (===) x y .&&. roundTripNoEnc (===) x y
76+
77+-- We test keys by encoding HashMap and Map with it
78+roundTripKey
79+ :: (Ord a, Hashable a, FromJSONKey a, ToJSONKey a, Show a)
80+ => a -> HashMap a Int -> Map a Int -> Property
81+roundTripKey _ h m = roundTripEq h h .&&. roundTripEq m m
82+
83+infix 4 ==~
84+(==~) :: (ApproxEq a, Show a) => a -> a -> Property
85+x ==~ y =
86+ counterexample (show x ++ " /= " ++ show y) (x =~ y)
87+
88+toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a, Show a) => a -> Property
89+toFromJSON x = case ifromJSON (toJSON x) of
90+ IError path err -> failure "fromJSON" (formatError path err) x
91+ ISuccess x' -> x === x'
92+
93+modifyFailureProp :: String -> String -> Bool
94+modifyFailureProp orig added =
95+ result == Error (added ++ orig)
96+ where
97+ parser = const $ modifyFailure (added ++) $ fail orig
98+ result :: Result ()
99+ result = parse parser ()
100+
101+parserThrowErrorProp :: String -> Property
102+parserThrowErrorProp msg =
103+ result === Error msg
104+ where
105+ parser = const $ parserThrowError [] msg
106+ result :: Result ()
107+ result = parse parser ()
108+
109+-- | Tests (also) that we catch the JSONPath and it has elements in the right order.
110+parserCatchErrorProp :: [String] -> String -> Property
111+parserCatchErrorProp path msg =
112+ result === Success ([I.Key "outer", I.Key "inner"] ++ jsonPath, msg)
113+ where
114+ parser = parserCatchError outer (curry pure)
115+
116+ outer = inner I.<?> I.Key "outer"
117+ inner = parserThrowError jsonPath msg I.<?> I.Key "inner"
118+
119+ result :: Result (I.JSONPath, String)
120+ result = parse (const parser) ()
121+
122+ jsonPath = map (I.Key . T.pack) path
123+
124+-- | Perform a structural comparison of the results of two encoding
125+-- methods. Compares decoded values to account for HashMap-driven
126+-- variation in JSON object key ordering.
127+sameAs :: (a -> Value) -> (a -> Encoding) -> a -> Property
128+sameAs toVal toEnc v =
129+ counterexample (show s) $
130+ eitherDecode s === Right (toVal v)
131+ where
132+ s = encodingToLazyByteString (toEnc v)
133+
134+sameAs1
135+ :: (forall a. LiftToJSON f a)
136+ -> (forall a. LiftToEncoding f a)
137+ -> f Int
138+ -> Property
139+sameAs1 toVal1 toEnc1 v = lhs === rhs
140+ where
141+ rhs = Right $ toVal1 toJSON (listValue toJSON) v
142+ lhs = eitherDecode . encodingToLazyByteString $
143+ toEnc1 toEncoding (listEncoding toEncoding) v
144+
145+sameAs1Agree
146+ :: ToJSON a
147+ => (f a -> Encoding)
148+ -> (forall b. LiftToEncoding f b)
149+ -> f a
150+ -> Property
151+sameAs1Agree toEnc toEnc1 v = rhs === lhs
152+ where
153+ rhs = encodingToLazyByteString $ toEnc v
154+ lhs = encodingToLazyByteString $ toEnc1 toEncoding (listEncoding toEncoding) v
155+
156+type P6 = Product6 Int Bool String (Approx Double) (Int, Approx Double) ()
157+type S4 = Sum4 Int8 ZonedTime T.Text (Map.Map String Int)
158+
159+--------------------------------------------------------------------------------
160+-- Value properties
161+--------------------------------------------------------------------------------
162+
163+-- | Add the formatted @Value@ to the printed counterexample when the property
164+-- fails.
165+checkValue :: Testable a => (Value -> a) -> Value -> Property
166+checkValue prop v = counterexample (L.unpack (encode v)) (prop v)
167+
168+isString :: Value -> Bool
169+isString (String _) = True
170+isString _ = False
171+
172+is2ElemArray :: Value -> Bool
173+is2ElemArray (Array v) = V.length v == 2 && isString (V.head v)
174+is2ElemArray _ = False
175+
176+isTaggedObjectValue :: Value -> Bool
177+isTaggedObjectValue (Object obj) = "tag" `H.member` obj &&
178+ "contents" `H.member` obj
179+isTaggedObjectValue _ = False
180+
181+isNullaryTaggedObject :: Value -> Bool
182+isNullaryTaggedObject obj = isTaggedObject' obj && isObjectWithSingleField obj
183+
184+isTaggedObject :: Value -> Property
185+isTaggedObject = checkValue isTaggedObject'
186+
187+isTaggedObject' :: Value -> Bool
188+isTaggedObject' (Object obj) = "tag" `H.member` obj
189+isTaggedObject' _ = False
190+
191+isObjectWithSingleField :: Value -> Bool
192+isObjectWithSingleField (Object obj) = H.size obj == 1
193+isObjectWithSingleField _ = False
194+
195+-- | is untaggedValue of EitherTextInt
196+isUntaggedValueETI :: Value -> Bool
197+isUntaggedValueETI (String s)
198+ | s == "nonenullary" = True
199+isUntaggedValueETI (Bool _) = True
200+isUntaggedValueETI (Number _) = True
201+isUntaggedValueETI (Array a) = length a == 2
202+isUntaggedValueETI _ = False
203+
204+isEmptyArray :: Value -> Property
205+isEmptyArray = checkValue isEmptyArray'
206+
207+isEmptyArray' :: Value -> Bool
208+isEmptyArray' = (Array mempty ==)
tests/Properties.hs+14−512
@@ -1,327 +1,28 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE NoImplicitPrelude #-}
2−{-# LANGUAGE FlexibleContexts #-} -- For arbitrary Compose
3−{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE RankNTypes #-}
5−{-# LANGUAGE ScopedTypeVariables #-}
6
2
7module Properties (module Properties) where
3module Properties (module Properties) where
8
4
9−import Prelude ()
10import Prelude.Compat
5import Prelude.Compat
11
6
12−import Control.Applicative (Const)
13−import Data.Aeson (eitherDecode, encode)
14−import Data.Aeson.Encoding (encodingToLazyByteString)
15−import Data.Aeson.Internal (IResult(..), formatError, ifromJSON, iparse)
16−import qualified Data.Aeson.Internal as I
17−import Data.Aeson.Parser (value)
18−import Data.Aeson.Types
19−import Data.DList (DList)
20−import Data.Functor.Compose (Compose (..))
21−import Data.HashMap.Strict (HashMap)
22−import Data.Hashable (Hashable)
23−import Data.Int (Int8)
24−import Data.List.NonEmpty (NonEmpty)
25−import Data.Map (Map)
26−import Data.Proxy (Proxy)
27−import Data.Ratio (Ratio)
28−import Data.Sequence (Seq)
29−import Data.Tagged (Tagged)
30−import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)
31−import Data.Version (Version)
32−import Encoders
33import Instances ()
7import Instances ()
34−import Numeric.Natural (Natural)
35−import Test.Framework (Test, testGroup)
8+import Test.Tasty (TestTree, testGroup)
36−import Test.Framework.Providers.QuickCheck2 (testProperty)
9+import Test.Tasty.QuickCheck (testProperty)
37−import Test.QuickCheck (Arbitrary(..), Property, Testable, (===), (.&&.), counterexample)
10+import Test.QuickCheck (Property)
38−import Types
11+import PropUtils
39−import qualified Data.Attoparsec.Lazy as L
12+import PropertyGeneric
40−import qualified Data.ByteString.Lazy.Char8 as L
13+import PropertyKeys
41−import qualified Data.HashMap.Strict as H
14+import PropertyRoundTrip
42−import qualified Data.Map as Map
15+import PropertyTH
43−import qualified Data.Text as T
44−import qualified Data.Text.Lazy as LT
45−import qualified Data.UUID.Types as UUID
46−import qualified Data.Vector as V
47
16
48−encodeDouble :: Double -> Double -> Property
49−encodeDouble num denom
50− | isInfinite d || isNaN d = encode d === "null"
51− | otherwise = (read . L.unpack . encode) d === d
52− where d = num / denom
53
17
54−encodeInteger :: Integer -> Property
55−encodeInteger i = encode i === L.pack (show i)
56−
57−toParseJSON :: (Eq a, Show a) =>
58− (Value -> Parser a) -> (a -> Value) -> a -> Property
59−toParseJSON parsejson tojson x =
60− case iparse parsejson . tojson $ x of
61− IError path msg -> failure "parse" (formatError path msg) x
62− ISuccess x' -> x === x'
63−
64−toParseJSON1
65− :: (Eq (f Int), Show (f Int))
66− => (forall a. LiftParseJSON f a)
67− -> (forall a. LiftToJSON f a)
68− -> f Int
69− -> Property
70−toParseJSON1 parsejson1 tojson1 = toParseJSON parsejson tojson
71− where
72− parsejson = parsejson1 parseJSON (listParser parseJSON)
73− tojson = tojson1 toJSON (listValue toJSON)
74−
75−roundTripEnc :: (FromJSON a, ToJSON a, Show a) =>
76− (a -> a -> Property) -> a -> a -> Property
77−roundTripEnc eq _ i =
78− case fmap ifromJSON . L.parse value . encode $ i of
79− L.Done _ (ISuccess v) -> v `eq` i
80− L.Done _ (IError path err) -> failure "fromJSON" (formatError path err) i
81− L.Fail _ _ err -> failure "parse" err i
82−
83−roundTripNoEnc :: (FromJSON a, ToJSON a, Show a) =>
84− (a -> a -> Property) -> a -> a -> Property
85−roundTripNoEnc eq _ i =
86− case ifromJSON . toJSON $ i of
87− (ISuccess v) -> v `eq` i
88− (IError path err) -> failure "fromJSON" (formatError path err) i
89−
90−roundTripEq :: (Eq a, FromJSON a, ToJSON a, Show a) => a -> a -> Property
91−roundTripEq x y = roundTripEnc (===) x y .&&. roundTripNoEnc (===) x y
92−
93−-- We test keys by encoding HashMap and Map with it
94−roundTripKey
95− :: (Ord a, Hashable a, FromJSONKey a, ToJSONKey a, Show a)
96− => a -> HashMap a Int -> Map a Int -> Property
97−roundTripKey _ h m = roundTripEq h h .&&. roundTripEq m m
98−
99−infix 4 ==~
100−(==~) :: (ApproxEq a, Show a) => a -> a -> Property
101−x ==~ y =
102− counterexample (show x ++ " /= " ++ show y) (x =~ y)
103−
104−toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a, Show a) => a -> Property
105−toFromJSON x = case ifromJSON (toJSON x) of
106− IError path err -> failure "fromJSON" (formatError path err) x
107− ISuccess x' -> x === x'
108−
109−modifyFailureProp :: String -> String -> Bool
110−modifyFailureProp orig added =
111− result == Error (added ++ orig)
112− where
113− parser = const $ modifyFailure (added ++) $ fail orig
114− result :: Result ()
115− result = parse parser ()
116−
117−parserThrowErrorProp :: String -> Property
118−parserThrowErrorProp msg =
119− result === Error msg
120− where
121− parser = const $ parserThrowError [] msg
122− result :: Result ()
123− result = parse parser ()
124−
125−-- | Tests (also) that we catch the JSONPath and it has elements in the right order.
126−parserCatchErrorProp :: [String] -> String -> Property
127−parserCatchErrorProp path msg =
128− result === Success ([I.Key "outer", I.Key "inner"] ++ jsonPath, msg)
129− where
130− parser = parserCatchError outer (curry pure)
131−
132− outer = inner I.<?> I.Key "outer"
133− inner = parserThrowError jsonPath msg I.<?> I.Key "inner"
134−
135− result :: Result (I.JSONPath, String)
136− result = parse (const parser) ()
137−
138− jsonPath = map (I.Key . T.pack) path
139−
140−-- | Perform a structural comparison of the results of two encoding
141−-- methods. Compares decoded values to account for HashMap-driven
142−-- variation in JSON object key ordering.
143−sameAs :: (a -> Value) -> (a -> Encoding) -> a -> Property
144−sameAs toVal toEnc v =
145− eitherDecode (encodingToLazyByteString (toEnc v)) === Right (toVal v)
146−
147−sameAs1
148− :: (forall a. LiftToJSON f a)
149− -> (forall a. LiftToEncoding f a)
150− -> f Int
151− -> Property
152−sameAs1 toVal1 toEnc1 v = lhs === rhs
153− where
154− rhs = Right $ toVal1 toJSON (listValue toJSON) v
155− lhs = eitherDecode . encodingToLazyByteString $
156− toEnc1 toEncoding (listEncoding toEncoding) v
157−
158−sameAs1Agree
159− :: ToJSON a
160− => (f a -> Encoding)
161− -> (forall b. LiftToEncoding f b)
162− -> f a
163− -> Property
164−sameAs1Agree toEnc toEnc1 v = rhs === lhs
165− where
166− rhs = encodingToLazyByteString $ toEnc v
167− lhs = encodingToLazyByteString $ toEnc1 toEncoding (listEncoding toEncoding) v
168−
169−type P6 = Product6 Int Bool String (Approx Double) (Int, Approx Double) ()
170−type S4 = Sum4 Int8 ZonedTime T.Text (Map.Map String Int)
171−
172−--------------------------------------------------------------------------------
173−-- Value properties
174−--------------------------------------------------------------------------------
175−
176−-- | Add the formatted @Value@ to the printed counterexample when the property
177−-- fails.
178−checkValue :: Testable a => (Value -> a) -> Value -> Property
179−checkValue prop v = counterexample (L.unpack (encode v)) (prop v)
180−
181−isString :: Value -> Bool
182−isString (String _) = True
183−isString _ = False
184−
185−is2ElemArray :: Value -> Bool
186−is2ElemArray (Array v) = V.length v == 2 && isString (V.head v)
187−is2ElemArray _ = False
188−
189−isTaggedObjectValue :: Value -> Bool
190−isTaggedObjectValue (Object obj) = "tag" `H.member` obj &&
191− "contents" `H.member` obj
192−isTaggedObjectValue _ = False
193−
194−isNullaryTaggedObject :: Value -> Bool
195−isNullaryTaggedObject obj = isTaggedObject' obj && isObjectWithSingleField obj
196−
197−isTaggedObject :: Value -> Property
198−isTaggedObject = checkValue isTaggedObject'
199−
200−isTaggedObject' :: Value -> Bool
201−isTaggedObject' (Object obj) = "tag" `H.member` obj
202−isTaggedObject' _ = False
203−
204−isObjectWithSingleField :: Value -> Bool
205−isObjectWithSingleField (Object obj) = H.size obj == 1
206−isObjectWithSingleField _ = False
207−
208−-- | is untaggedValue of EitherTextInt
209−isUntaggedValueETI :: Value -> Bool
210−isUntaggedValueETI (String s)
211− | s == "nonenullary" = True
212−isUntaggedValueETI (Bool _) = True
213−isUntaggedValueETI (Number _) = True
214−isUntaggedValueETI (Array a) = length a == 2
215−isUntaggedValueETI _ = False
216−
217−isEmptyArray :: Value -> Property
218−isEmptyArray = checkValue isEmptyArray'
219−
220−isEmptyArray' :: Value -> Bool
221−isEmptyArray' = (Array mempty ==)
222−
223−
224−--------------------------------------------------------------------------------
225−
226−
227−tests :: Test
18+tests :: TestTree
228tests = testGroup "properties" [
19tests = testGroup "properties" [
229 testGroup "encode" [
20 testGroup "encode" [
230 testProperty "encodeDouble" encodeDouble
21 testProperty "encodeDouble" encodeDouble
231 , testProperty "encodeInteger" encodeInteger
22 , testProperty "encodeInteger" encodeInteger
232 ]
23 ]
233− , testGroup "roundTrip" [
24+ , roundTripTests
234− testProperty "Bool" $ roundTripEq True
25+ , keysTests
235− , testProperty "Double" $ roundTripEq (1 :: Approx Double)
236− , testProperty "Int" $ roundTripEq (1 :: Int)
237− , testProperty "NonEmpty Char" $ roundTripEq (undefined :: NonEmpty Char)
238− , testProperty "Integer" $ roundTripEq (1 :: Integer)
239− , testProperty "String" $ roundTripEq ("" :: String)
240− , testProperty "Text" $ roundTripEq T.empty
241− , testProperty "Lazy Text" $ roundTripEq LT.empty
242− , testProperty "Foo" $ roundTripEq (undefined :: Foo)
243− , testProperty "Day" $ roundTripEq (undefined :: Day)
244− , testProperty "BCE Day" $ roundTripEq (undefined :: BCEDay)
245− , testProperty "DotNetTime" $ roundTripEq (undefined :: Approx DotNetTime)
246− , testProperty "LocalTime" $ roundTripEq (undefined :: LocalTime)
247− , testProperty "TimeOfDay" $ roundTripEq (undefined :: TimeOfDay)
248− , testProperty "UTCTime" $ roundTripEq (undefined :: UTCTime)
249− , testProperty "ZonedTime" $ roundTripEq (undefined :: ZonedTime)
250− , testProperty "NominalDiffTime" $ roundTripEq (undefined :: NominalDiffTime)
251− , testProperty "DiffTime" $ roundTripEq (undefined :: DiffTime)
252− , testProperty "Version" $ roundTripEq (undefined :: Version)
253− , testProperty "Natural" $ roundTripEq (undefined :: Natural)
254− , testProperty "Proxy" $ roundTripEq (undefined :: Proxy Int)
255− , testProperty "Tagged" $ roundTripEq (undefined :: Tagged Int Char)
256− , testProperty "Const" $ roundTripEq (undefined :: Const Int Char)
257− , testProperty "DList" $ roundTripEq (undefined :: DList Int)
258− , testProperty "Seq" $ roundTripEq (undefined :: Seq Int)
259− , testProperty "Rational" $ roundTripEq (undefined :: Rational)
260− , testProperty "Ratio Int" $ roundTripEq (undefined :: Ratio Int)
261− , testProperty "UUID" $ roundTripEq UUID.nil
262− , testGroup "functors"
263− [ testProperty "Identity Char" $ roundTripEq (undefined :: I Int)
264−
265− , testProperty "Identity Char" $ roundTripEq (undefined :: I Char)
266− , testProperty "Identity [Char]" $ roundTripEq (undefined :: I String)
267− , testProperty "[Identity Char]" $ roundTripEq (undefined :: [I Char])
268−
269− , testProperty "Compose I I Int" $ roundTripEq (undefined :: LogScaled (Compose I I Int))
270− , testProperty "Compose [] I Int" $ roundTripEq (undefined :: LogScaled (Compose [] I Int))
271− , testProperty "Compose I [] Int" $ roundTripEq (undefined :: LogScaled (Compose I [] Int))
272− , testProperty "Compose [] [] Int" $ roundTripEq (undefined :: LogScaled (Compose [] [] Int))
273−
274− , testProperty "Compose I I Char" $ roundTripEq (undefined :: LogScaled (Compose I I Char))
275− , testProperty "Compose [] I Char" $ roundTripEq (undefined :: LogScaled (Compose [] I Char))
276− , testProperty "Compose I [] Char" $ roundTripEq (undefined :: LogScaled (Compose I [] Char))
277− , testProperty "Compose [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose [] [] Char))
278−
279− , testProperty "Compose3 I I I Char" $ roundTripEq (undefined :: LogScaled (Compose3 I I I Char))
280− , testProperty "Compose3 I [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3 I [] I Char))
281− , testProperty "Compose3 I I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 I I [] Char))
282− , testProperty "Compose3 I [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 I [] [] Char))
283− , testProperty "Compose3 [] I I Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] I I Char))
284− , testProperty "Compose3 [] [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] [] I Char))
285− , testProperty "Compose3 [] I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] I [] Char))
286− , testProperty "Compose3 [] [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] [] [] Char))
287−
288− , testProperty "Compose3' I I I Char" $ roundTripEq (undefined :: LogScaled (Compose3' I I I Char))
289− , testProperty "Compose3' I [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3' I [] I Char))
290− , testProperty "Compose3' I I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' I I [] Char))
291− , testProperty "Compose3' I [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' I [] [] Char))
292− , testProperty "Compose3' [] I I Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] I I Char))
293− , testProperty "Compose3' [] [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] [] I Char))
294− , testProperty "Compose3' [] I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] I [] Char))
295− , testProperty "Compose3' [] [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] [] [] Char))
296− ]
297− , testGroup "ghcGenerics" [
298− testProperty "OneConstructor" $ roundTripEq OneConstructor
299− , testProperty "Product2" $ roundTripEq (undefined :: Product2 Int Bool)
300− , testProperty "Product6" $ roundTripEq (undefined :: P6)
301− , testProperty "Sum4" $ roundTripEq (undefined :: S4)
302− ]
303− ]
304− , testGroup "roundTrip Key"
305− [ testProperty "Bool" $ roundTripKey True
306− , testProperty "Text" $ roundTripKey (undefined :: T.Text)
307− , testProperty "String" $ roundTripKey (undefined :: String)
308− , testProperty "Int" $ roundTripKey (undefined :: Int)
309− , testProperty "[Text]" $ roundTripKey (undefined :: LogScaled [T.Text])
310− , testProperty "(Int,Char)" $ roundTripKey (undefined :: (Int,Char))
311− , testProperty "Integer" $ roundTripKey (undefined :: Integer)
312− , testProperty "Natural" $ roundTripKey (undefined :: Natural)
313− , testProperty "Float" $ roundTripKey (undefined :: Float)
314− , testProperty "Double" $ roundTripKey (undefined :: Double)
315−#if MIN_VERSION_base(4,7,0)
316− , testProperty "Day" $ roundTripKey (undefined :: Day)
317− , testProperty "LocalTime" $ roundTripKey (undefined :: LocalTime)
318− , testProperty "TimeOfDay" $ roundTripKey (undefined :: TimeOfDay)
319− , testProperty "UTCTime" $ roundTripKey (undefined :: UTCTime)
320−#endif
321− , testProperty "Version" $ roundTripKey (undefined :: Version)
322− , testProperty "Lazy Text" $ roundTripKey (undefined :: LT.Text)
323− , testProperty "UUID" $ roundTripKey UUID.nil
324− ]
325 , testGroup "toFromJSON" [
26 , testGroup "toFromJSON" [
326 testProperty "Integer" (toFromJSON :: Integer -> Property)
27 testProperty "Integer" (toFromJSON :: Integer -> Property)
327 , testProperty "Double" (toFromJSON :: Double -> Property)
28 , testProperty "Double" (toFromJSON :: Double -> Property)
@@ -334,205 +35,6 @@ tests = testGroup "properties" [
334 , testProperty "parserThrowError" parserThrowErrorProp
35 , testProperty "parserThrowError" parserThrowErrorProp
335 , testProperty "parserCatchError" parserCatchErrorProp
36 , testProperty "parserCatchError" parserCatchErrorProp
336 ]
37 ]
337− , testGroup "generic" [
38+ , genericTests
338− testGroup "toJSON" [
39+ , templateHaskellTests
339− testGroup "Nullary" [
340− testProperty "string" (isString . gNullaryToJSONString)
341− , testProperty "2ElemArray" (is2ElemArray . gNullaryToJSON2ElemArray)
342− , testProperty "TaggedObject" (isNullaryTaggedObject . gNullaryToJSONTaggedObject)
343− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . gNullaryToJSONObjectWithSingleField)
344− , testGroup "roundTrip" [
345− testProperty "string" (toParseJSON gNullaryParseJSONString gNullaryToJSONString)
346− , testProperty "2ElemArray" (toParseJSON gNullaryParseJSON2ElemArray gNullaryToJSON2ElemArray)
347− , testProperty "TaggedObject" (toParseJSON gNullaryParseJSONTaggedObject gNullaryToJSONTaggedObject)
348− , testProperty "ObjectWithSingleField" (toParseJSON gNullaryParseJSONObjectWithSingleField gNullaryToJSONObjectWithSingleField)
349− ]
350− ]
351− , testGroup "EitherTextInt" [
352− testProperty "UntaggedValue" (isUntaggedValueETI . gEitherTextIntToJSONUntaggedValue)
353− , testProperty "roundtrip" (toParseJSON gEitherTextIntParseJSONUntaggedValue gEitherTextIntToJSONUntaggedValue)
354− ]
355− , testGroup "SomeType" [
356− testProperty "2ElemArray" (is2ElemArray . gSomeTypeToJSON2ElemArray)
357− , testProperty "TaggedObject" (isTaggedObject . gSomeTypeToJSONTaggedObject)
358− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . gSomeTypeToJSONObjectWithSingleField)
359− , testGroup "roundTrip" [
360− testProperty "2ElemArray" (toParseJSON gSomeTypeParseJSON2ElemArray gSomeTypeToJSON2ElemArray)
361− , testProperty "TaggedObject" (toParseJSON gSomeTypeParseJSONTaggedObject gSomeTypeToJSONTaggedObject)
362− , testProperty "ObjectWithSingleField" (toParseJSON gSomeTypeParseJSONObjectWithSingleField gSomeTypeToJSONObjectWithSingleField)
363−
364−#if __GLASGOW_HASKELL__ >= 706
365− , testProperty "2ElemArray unary" (toParseJSON1 gSomeTypeLiftParseJSON2ElemArray gSomeTypeLiftToJSON2ElemArray)
366− , testProperty "TaggedObject unary" (toParseJSON1 gSomeTypeLiftParseJSONTaggedObject gSomeTypeLiftToJSONTaggedObject)
367− , testProperty "ObjectWithSingleField unary" (toParseJSON1 gSomeTypeLiftParseJSONObjectWithSingleField gSomeTypeLiftToJSONObjectWithSingleField)
368−#endif
369− ]
370− ]
371− , testGroup "OneConstructor" [
372− testProperty "default" (isEmptyArray . gOneConstructorToJSONDefault)
373− , testProperty "Tagged" (isTaggedObject . gOneConstructorToJSONTagged)
374− , testGroup "roundTrip" [
375− testProperty "default" (toParseJSON gOneConstructorParseJSONDefault gOneConstructorToJSONDefault)
376− , testProperty "Tagged" (toParseJSON gOneConstructorParseJSONTagged gOneConstructorToJSONTagged)
377− ]
378− ]
379− ]
380− , testGroup "toEncoding" [
381− testProperty "NullaryString" $
382− gNullaryToJSONString `sameAs` gNullaryToEncodingString
383− , testProperty "Nullary2ElemArray" $
384− gNullaryToJSON2ElemArray `sameAs` gNullaryToEncoding2ElemArray
385− , testProperty "NullaryTaggedObject" $
386− gNullaryToJSONTaggedObject `sameAs` gNullaryToEncodingTaggedObject
387− , testProperty "NullaryObjectWithSingleField" $
388− gNullaryToJSONObjectWithSingleField `sameAs`
389− gNullaryToEncodingObjectWithSingleField
390− -- , testProperty "ApproxUnwrap" $
391− -- gApproxToJSONUnwrap `sameAs` gApproxToEncodingUnwrap
392− , testProperty "ApproxDefault" $
393− gApproxToJSONDefault `sameAs` gApproxToEncodingDefault
394−
395− , testProperty "EitherTextInt UntaggedValue" $
396− gEitherTextIntToJSONUntaggedValue `sameAs` gEitherTextIntToEncodingUntaggedValue
397−
398− , testProperty "SomeType2ElemArray" $
399− gSomeTypeToJSON2ElemArray `sameAs` gSomeTypeToEncoding2ElemArray
400−#if __GLASGOW_HASKELL__ >= 706
401− , testProperty "SomeType2ElemArray unary" $
402− gSomeTypeLiftToJSON2ElemArray `sameAs1` gSomeTypeLiftToEncoding2ElemArray
403− , testProperty "SomeType2ElemArray unary agree" $
404− gSomeTypeToEncoding2ElemArray `sameAs1Agree` gSomeTypeLiftToEncoding2ElemArray
405−#endif
406−
407− , testProperty "SomeTypeTaggedObject" $
408− gSomeTypeToJSONTaggedObject `sameAs` gSomeTypeToEncodingTaggedObject
409−#if __GLASGOW_HASKELL__ >= 706
410− , testProperty "SomeTypeTaggedObject unary" $
411− gSomeTypeLiftToJSONTaggedObject `sameAs1` gSomeTypeLiftToEncodingTaggedObject
412− , testProperty "SomeTypeTaggedObject unary agree" $
413− gSomeTypeToEncodingTaggedObject `sameAs1Agree` gSomeTypeLiftToEncodingTaggedObject
414−#endif
415−
416− , testProperty "SomeTypeObjectWithSingleField" $
417− gSomeTypeToJSONObjectWithSingleField `sameAs` gSomeTypeToEncodingObjectWithSingleField
418−#if __GLASGOW_HASKELL__ >= 706
419− , testProperty "SomeTypeObjectWithSingleField unary" $
420− gSomeTypeLiftToJSONObjectWithSingleField `sameAs1` gSomeTypeLiftToEncodingObjectWithSingleField
421− , testProperty "SomeTypeObjectWithSingleField unary agree" $
422− gSomeTypeToEncodingObjectWithSingleField `sameAs1Agree` gSomeTypeLiftToEncodingObjectWithSingleField
423−#endif
424−
425− , testProperty "SomeTypeOmitNothingFields" $
426− gSomeTypeToJSONOmitNothingFields `sameAs` gSomeTypeToEncodingOmitNothingFields
427−
428− , testProperty "OneConstructorDefault" $
429− gOneConstructorToJSONDefault `sameAs` gOneConstructorToEncodingDefault
430− , testProperty "OneConstructorTagged" $
431− gOneConstructorToJSONTagged `sameAs` gOneConstructorToEncodingTagged
432− ]
433− ]
434− , testGroup "template-haskell" [
435− testGroup "toJSON" [
436− testGroup "Nullary" [
437− testProperty "string" (isString . thNullaryToJSONString)
438− , testProperty "2ElemArray" (is2ElemArray . thNullaryToJSON2ElemArray)
439− , testProperty "TaggedObject" (isNullaryTaggedObject . thNullaryToJSONTaggedObject)
440− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thNullaryToJSONObjectWithSingleField)
441−
442− , testGroup "roundTrip" [
443− testProperty "string" (toParseJSON thNullaryParseJSONString thNullaryToJSONString)
444− , testProperty "2ElemArray" (toParseJSON thNullaryParseJSON2ElemArray thNullaryToJSON2ElemArray)
445− , testProperty "TaggedObject" (toParseJSON thNullaryParseJSONTaggedObject thNullaryToJSONTaggedObject)
446− , testProperty "ObjectWithSingleField" (toParseJSON thNullaryParseJSONObjectWithSingleField thNullaryToJSONObjectWithSingleField)
447− ]
448− ]
449− , testGroup "EitherTextInt" [
450− testProperty "UntaggedValue" (isUntaggedValueETI . thEitherTextIntToJSONUntaggedValue)
451− , testProperty "roundtrip" (toParseJSON thEitherTextIntParseJSONUntaggedValue thEitherTextIntToJSONUntaggedValue)
452− ]
453− , testGroup "SomeType" [
454− testProperty "2ElemArray" (is2ElemArray . thSomeTypeToJSON2ElemArray)
455− , testProperty "TaggedObject" (isTaggedObject . thSomeTypeToJSONTaggedObject)
456− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thSomeTypeToJSONObjectWithSingleField)
457− , testGroup "roundTrip" [
458− testProperty "2ElemArray" (toParseJSON thSomeTypeParseJSON2ElemArray thSomeTypeToJSON2ElemArray)
459− , testProperty "TaggedObject" (toParseJSON thSomeTypeParseJSONTaggedObject thSomeTypeToJSONTaggedObject)
460− , testProperty "ObjectWithSingleField" (toParseJSON thSomeTypeParseJSONObjectWithSingleField thSomeTypeToJSONObjectWithSingleField)
461−
462− , testProperty "2ElemArray unary" (toParseJSON1 thSomeTypeLiftParseJSON2ElemArray thSomeTypeLiftToJSON2ElemArray)
463− , testProperty "TaggedObject unary" (toParseJSON1 thSomeTypeLiftParseJSONTaggedObject thSomeTypeLiftToJSONTaggedObject)
464− , testProperty "ObjectWithSingleField unary" (toParseJSON1 thSomeTypeLiftParseJSONObjectWithSingleField thSomeTypeLiftToJSONObjectWithSingleField)
465−
466− ]
467− ]
468− , testGroup "Approx" [
469− testProperty "string" (isString . thApproxToJSONUnwrap)
470− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thApproxToJSONDefault)
471− , testGroup "roundTrip" [
472− testProperty "string" (toParseJSON thApproxParseJSONUnwrap thApproxToJSONUnwrap)
473− , testProperty "ObjectWithSingleField" (toParseJSON thApproxParseJSONDefault thApproxToJSONDefault)
474− ]
475− ]
476− , testGroup "GADT" [
477− testProperty "string" (isString . thGADTToJSONUnwrap)
478− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thGADTToJSONDefault)
479− , testGroup "roundTrip" [
480− testProperty "string" (toParseJSON thGADTParseJSONUnwrap thGADTToJSONUnwrap)
481− , testProperty "ObjectWithSingleField" (toParseJSON thGADTParseJSONDefault thGADTToJSONDefault)
482− ]
483− ]
484− , testGroup "OneConstructor" [
485− testProperty "default" (isEmptyArray . thOneConstructorToJSONDefault)
486− , testProperty "Tagged" (isTaggedObject . thOneConstructorToJSONTagged)
487− , testGroup "roundTrip" [
488− testProperty "default" (toParseJSON thOneConstructorParseJSONDefault thOneConstructorToJSONDefault)
489− , testProperty "Tagged" (toParseJSON thOneConstructorParseJSONTagged thOneConstructorToJSONTagged)
490− ]
491− ]
492− ]
493− , testGroup "toEncoding" [
494− testProperty "NullaryString" $
495− thNullaryToJSONString `sameAs` thNullaryToEncodingString
496− , testProperty "Nullary2ElemArray" $
497− thNullaryToJSON2ElemArray `sameAs` thNullaryToEncoding2ElemArray
498− , testProperty "NullaryTaggedObject" $
499− thNullaryToJSONTaggedObject `sameAs` thNullaryToEncodingTaggedObject
500− , testProperty "NullaryObjectWithSingleField" $
501− thNullaryToJSONObjectWithSingleField `sameAs`
502− thNullaryToEncodingObjectWithSingleField
503− , testProperty "ApproxUnwrap" $
504− thApproxToJSONUnwrap `sameAs` thApproxToEncodingUnwrap
505− , testProperty "ApproxDefault" $
506− thApproxToJSONDefault `sameAs` thApproxToEncodingDefault
507−
508− , testProperty "EitherTextInt UntaggedValue" $
509− thEitherTextIntToJSONUntaggedValue `sameAs` thEitherTextIntToEncodingUntaggedValue
510−
511− , testProperty "SomeType2ElemArray" $
512− thSomeTypeToJSON2ElemArray `sameAs` thSomeTypeToEncoding2ElemArray
513− , testProperty "SomeType2ElemArray unary" $
514− thSomeTypeLiftToJSON2ElemArray `sameAs1` thSomeTypeLiftToEncoding2ElemArray
515− , testProperty "SomeType2ElemArray unary agree" $
516− thSomeTypeToEncoding2ElemArray `sameAs1Agree` thSomeTypeLiftToEncoding2ElemArray
517−
518− , testProperty "SomeTypeTaggedObject" $
519− thSomeTypeToJSONTaggedObject `sameAs` thSomeTypeToEncodingTaggedObject
520− , testProperty "SomeTypeTaggedObject unary" $
521− thSomeTypeLiftToJSONTaggedObject `sameAs1` thSomeTypeLiftToEncodingTaggedObject
522− , testProperty "SomeTypeTaggedObject unary agree" $
523− thSomeTypeToEncodingTaggedObject `sameAs1Agree` thSomeTypeLiftToEncodingTaggedObject
524−
525− , testProperty "SomeTypeObjectWithSingleField" $
526− thSomeTypeToJSONObjectWithSingleField `sameAs` thSomeTypeToEncodingObjectWithSingleField
527− , testProperty "SomeTypeObjectWithSingleField unary" $
528− thSomeTypeLiftToJSONObjectWithSingleField `sameAs1` thSomeTypeLiftToEncodingObjectWithSingleField
529− , testProperty "SomeTypeObjectWithSingleField unary agree" $
530− thSomeTypeToEncodingObjectWithSingleField `sameAs1Agree` thSomeTypeLiftToEncodingObjectWithSingleField
531−
532− , testProperty "OneConstructorDefault" $
533− thOneConstructorToJSONDefault `sameAs` thOneConstructorToEncodingDefault
534− , testProperty "OneConstructorTagged" $
535− thOneConstructorToJSONTagged `sameAs` thOneConstructorToEncodingTagged
536− ]
537− ]
538 ]
40 ]
tests/PropertyGeneric.hsnew file+116
@@ -0,0 +1,116 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
3+
4+module PropertyGeneric ( genericTests ) where
5+
6+import Prelude.Compat
7+
8+import Data.Semigroup (Option(..))
9+import Encoders
10+import Instances ()
11+import Test.Tasty (TestTree, testGroup)
12+import Test.Tasty.QuickCheck (testProperty)
13+import Test.QuickCheck ( (===) )
14+import Types
15+import PropUtils
16+
17+
18+genericTests :: TestTree
19+genericTests =
20+ testGroup "generic" [
21+ testGroup "toJSON" [
22+ testGroup "Nullary" [
23+ testProperty "string" (isString . gNullaryToJSONString)
24+ , testProperty "2ElemArray" (is2ElemArray . gNullaryToJSON2ElemArray)
25+ , testProperty "TaggedObject" (isNullaryTaggedObject . gNullaryToJSONTaggedObject)
26+ , testProperty "ObjectWithSingleField" (isObjectWithSingleField . gNullaryToJSONObjectWithSingleField)
27+ , testGroup "roundTrip" [
28+ testProperty "string" (toParseJSON gNullaryParseJSONString gNullaryToJSONString)
29+ , testProperty "2ElemArray" (toParseJSON gNullaryParseJSON2ElemArray gNullaryToJSON2ElemArray)
30+ , testProperty "TaggedObject" (toParseJSON gNullaryParseJSONTaggedObject gNullaryToJSONTaggedObject)
31+ , testProperty "ObjectWithSingleField" (toParseJSON gNullaryParseJSONObjectWithSingleField gNullaryToJSONObjectWithSingleField)
32+ ]
33+ ]
34+ , testGroup "EitherTextInt" [
35+ testProperty "UntaggedValue" (isUntaggedValueETI . gEitherTextIntToJSONUntaggedValue)
36+ , testProperty "roundtrip" (toParseJSON gEitherTextIntParseJSONUntaggedValue gEitherTextIntToJSONUntaggedValue)
37+ ]
38+ , testGroup "SomeType" [
39+ testProperty "2ElemArray" (is2ElemArray . gSomeTypeToJSON2ElemArray)
40+ , testProperty "TaggedObject" (isTaggedObject . gSomeTypeToJSONTaggedObject)
41+ , testProperty "ObjectWithSingleField" (isObjectWithSingleField . gSomeTypeToJSONObjectWithSingleField)
42+ , testGroup "roundTrip" [
43+ testProperty "2ElemArray" (toParseJSON gSomeTypeParseJSON2ElemArray gSomeTypeToJSON2ElemArray)
44+ , testProperty "TaggedObject" (toParseJSON gSomeTypeParseJSONTaggedObject gSomeTypeToJSONTaggedObject)
45+ , testProperty "ObjectWithSingleField" (toParseJSON gSomeTypeParseJSONObjectWithSingleField gSomeTypeToJSONObjectWithSingleField)
46+
47+ , testProperty "2ElemArray unary" (toParseJSON1 gSomeTypeLiftParseJSON2ElemArray gSomeTypeLiftToJSON2ElemArray)
48+ , testProperty "TaggedObject unary" (toParseJSON1 gSomeTypeLiftParseJSONTaggedObject gSomeTypeLiftToJSONTaggedObject)
49+ , testProperty "ObjectWithSingleField unary" (toParseJSON1 gSomeTypeLiftParseJSONObjectWithSingleField gSomeTypeLiftToJSONObjectWithSingleField)
50+ ]
51+ ]
52+ , testGroup "OneConstructor" [
53+ testProperty "default" (isEmptyArray . gOneConstructorToJSONDefault)
54+ , testProperty "Tagged" (isTaggedObject . gOneConstructorToJSONTagged)
55+ , testGroup "roundTrip" [
56+ testProperty "default" (toParseJSON gOneConstructorParseJSONDefault gOneConstructorToJSONDefault)
57+ , testProperty "Tagged" (toParseJSON gOneConstructorParseJSONTagged gOneConstructorToJSONTagged)
58+ ]
59+ ]
60+ , testGroup "OptionField" [
61+ testProperty "like Maybe" $
62+ \x -> gOptionFieldToJSON (OptionField (Option x)) === thMaybeFieldToJSON (MaybeField x)
63+ , testProperty "roundTrip" (toParseJSON gOptionFieldParseJSON gOptionFieldToJSON)
64+ ]
65+ ]
66+ , testGroup "toEncoding" [
67+ testProperty "NullaryString" $
68+ gNullaryToJSONString `sameAs` gNullaryToEncodingString
69+ , testProperty "Nullary2ElemArray" $
70+ gNullaryToJSON2ElemArray `sameAs` gNullaryToEncoding2ElemArray
71+ , testProperty "NullaryTaggedObject" $
72+ gNullaryToJSONTaggedObject `sameAs` gNullaryToEncodingTaggedObject
73+ , testProperty "NullaryObjectWithSingleField" $
74+ gNullaryToJSONObjectWithSingleField `sameAs`
75+ gNullaryToEncodingObjectWithSingleField
76+ -- , testProperty "ApproxUnwrap" $
77+ -- gApproxToJSONUnwrap `sameAs` gApproxToEncodingUnwrap
78+ , testProperty "ApproxDefault" $
79+ gApproxToJSONDefault `sameAs` gApproxToEncodingDefault
80+
81+ , testProperty "EitherTextInt UntaggedValue" $
82+ gEitherTextIntToJSONUntaggedValue `sameAs` gEitherTextIntToEncodingUntaggedValue
83+
84+ , testProperty "SomeType2ElemArray" $
85+ gSomeTypeToJSON2ElemArray `sameAs` gSomeTypeToEncoding2ElemArray
86+ , testProperty "SomeType2ElemArray unary" $
87+ gSomeTypeLiftToJSON2ElemArray `sameAs1` gSomeTypeLiftToEncoding2ElemArray
88+ , testProperty "SomeType2ElemArray unary agree" $
89+ gSomeTypeToEncoding2ElemArray `sameAs1Agree` gSomeTypeLiftToEncoding2ElemArray
90+
91+ , testProperty "SomeTypeTaggedObject" $
92+ gSomeTypeToJSONTaggedObject `sameAs` gSomeTypeToEncodingTaggedObject
93+ , testProperty "SomeTypeTaggedObject unary" $
94+ gSomeTypeLiftToJSONTaggedObject `sameAs1` gSomeTypeLiftToEncodingTaggedObject
95+ , testProperty "SomeTypeTaggedObject unary agree" $
96+ gSomeTypeToEncodingTaggedObject `sameAs1Agree` gSomeTypeLiftToEncodingTaggedObject
97+
98+ , testProperty "SomeTypeObjectWithSingleField" $
99+ gSomeTypeToJSONObjectWithSingleField `sameAs` gSomeTypeToEncodingObjectWithSingleField
100+ , testProperty "SomeTypeObjectWithSingleField unary" $
101+ gSomeTypeLiftToJSONObjectWithSingleField `sameAs1` gSomeTypeLiftToEncodingObjectWithSingleField
102+ , testProperty "SomeTypeObjectWithSingleField unary agree" $
103+ gSomeTypeToEncodingObjectWithSingleField `sameAs1Agree` gSomeTypeLiftToEncodingObjectWithSingleField
104+
105+ , testProperty "SomeTypeOmitNothingFields" $
106+ gSomeTypeToJSONOmitNothingFields `sameAs` gSomeTypeToEncodingOmitNothingFields
107+
108+ , testProperty "OneConstructorDefault" $
109+ gOneConstructorToJSONDefault `sameAs` gOneConstructorToEncodingDefault
110+ , testProperty "OneConstructorTagged" $
111+ gOneConstructorToJSONTagged `sameAs` gOneConstructorToEncodingTagged
112+
113+ , testProperty "OptionField" $
114+ gOptionFieldToJSON `sameAs` gOptionFieldToEncoding
115+ ]
116+ ]
tests/PropertyKeys.hsnew file+40
@@ -0,0 +1,40 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
3+module PropertyKeys ( keysTests ) where
4+
5+import Prelude.Compat
6+
7+import Data.Time (Day, LocalTime, TimeOfDay, UTCTime)
8+import Data.Version (Version)
9+import Instances ()
10+import Numeric.Natural (Natural)
11+import Test.Tasty (TestTree, testGroup)
12+import Test.Tasty.QuickCheck (testProperty)
13+import Types
14+import qualified Data.Text as T
15+import qualified Data.Text.Lazy as LT
16+import qualified Data.UUID.Types as UUID
17+import PropUtils
18+
19+
20+keysTests :: TestTree
21+keysTests =
22+ testGroup "roundTrip Key"
23+ [ testProperty "Bool" $ roundTripKey True
24+ , testProperty "Text" $ roundTripKey (undefined :: T.Text)
25+ , testProperty "String" $ roundTripKey (undefined :: String)
26+ , testProperty "Int" $ roundTripKey (undefined :: Int)
27+ , testProperty "[Text]" $ roundTripKey (undefined :: LogScaled [T.Text])
28+ , testProperty "(Int,Char)" $ roundTripKey (undefined :: (Int,Char))
29+ , testProperty "Integer" $ roundTripKey (undefined :: Integer)
30+ , testProperty "Natural" $ roundTripKey (undefined :: Natural)
31+ , testProperty "Float" $ roundTripKey (undefined :: Float)
32+ , testProperty "Double" $ roundTripKey (undefined :: Double)
33+ , testProperty "Day" $ roundTripKey (undefined :: Day)
34+ , testProperty "LocalTime" $ roundTripKey (undefined :: LocalTime)
35+ , testProperty "TimeOfDay" $ roundTripKey (undefined :: TimeOfDay)
36+ , testProperty "UTCTime" $ roundTripKey (undefined :: UTCTime)
37+ , testProperty "Version" $ roundTripKey (undefined :: Version)
38+ , testProperty "Lazy Text" $ roundTripKey (undefined :: LT.Text)
39+ , testProperty "UUID" $ roundTripKey UUID.nil
40+ ]
tests/PropertyRTFunctors.hsnew file+51
@@ -0,0 +1,51 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
3+module PropertyRTFunctors ( roundTripFunctorsTests ) where
4+
5+import Prelude.Compat
6+
7+import Data.Functor.Compose (Compose (..))
8+import Instances ()
9+import Test.Tasty (TestTree, testGroup)
10+import Test.Tasty.QuickCheck (testProperty)
11+import Types
12+import PropUtils
13+
14+
15+roundTripFunctorsTests :: TestTree
16+roundTripFunctorsTests =
17+ testGroup "functors"
18+ [ testProperty "Identity Char" $ roundTripEq (undefined :: I Int)
19+
20+ , testProperty "Identity Char" $ roundTripEq (undefined :: I Char)
21+ , testProperty "Identity [Char]" $ roundTripEq (undefined :: I String)
22+ , testProperty "[Identity Char]" $ roundTripEq (undefined :: [I Char])
23+
24+ , testProperty "Compose I I Int" $ roundTripEq (undefined :: LogScaled (Compose I I Int))
25+ , testProperty "Compose [] I Int" $ roundTripEq (undefined :: LogScaled (Compose [] I Int))
26+ , testProperty "Compose I [] Int" $ roundTripEq (undefined :: LogScaled (Compose I [] Int))
27+ , testProperty "Compose [] [] Int" $ roundTripEq (undefined :: LogScaled (Compose [] [] Int))
28+
29+ , testProperty "Compose I I Char" $ roundTripEq (undefined :: LogScaled (Compose I I Char))
30+ , testProperty "Compose [] I Char" $ roundTripEq (undefined :: LogScaled (Compose [] I Char))
31+ , testProperty "Compose I [] Char" $ roundTripEq (undefined :: LogScaled (Compose I [] Char))
32+ , testProperty "Compose [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose [] [] Char))
33+
34+ , testProperty "Compose3 I I I Char" $ roundTripEq (undefined :: LogScaled (Compose3 I I I Char))
35+ , testProperty "Compose3 I [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3 I [] I Char))
36+ , testProperty "Compose3 I I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 I I [] Char))
37+ , testProperty "Compose3 I [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 I [] [] Char))
38+ , testProperty "Compose3 [] I I Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] I I Char))
39+ , testProperty "Compose3 [] [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] [] I Char))
40+ , testProperty "Compose3 [] I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] I [] Char))
41+ , testProperty "Compose3 [] [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] [] [] Char))
42+
43+ , testProperty "Compose3' I I I Char" $ roundTripEq (undefined :: LogScaled (Compose3' I I I Char))
44+ , testProperty "Compose3' I [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3' I [] I Char))
45+ , testProperty "Compose3' I I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' I I [] Char))
46+ , testProperty "Compose3' I [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' I [] [] Char))
47+ , testProperty "Compose3' [] I I Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] I I Char))
48+ , testProperty "Compose3' [] [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] [] I Char))
49+ , testProperty "Compose3' [] I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] I [] Char))
50+ , testProperty "Compose3' [] [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] [] [] Char))
51+ ]
tests/PropertyRoundTrip.hsnew file+74
@@ -0,0 +1,74 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
3+module PropertyRoundTrip ( roundTripTests ) where
4+
5+import Prelude.Compat
6+
7+import Control.Applicative (Const)
8+import Data.Aeson.Types
9+import Data.DList (DList)
10+import Data.List.NonEmpty (NonEmpty)
11+import Data.Proxy (Proxy)
12+import Data.Ratio (Ratio)
13+import Data.Sequence (Seq)
14+import Data.Tagged (Tagged)
15+import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)
16+import Data.Version (Version)
17+import Data.Time.Calendar.Compat (CalendarDiffDays, DayOfWeek)
18+import Data.Time.LocalTime.Compat (CalendarDiffTime)
19+import Data.Time.Clock.System.Compat (SystemTime)
20+import Instances ()
21+import Numeric.Natural (Natural)
22+import Test.Tasty (TestTree, testGroup)
23+import Test.Tasty.QuickCheck (testProperty)
24+import Types
25+import qualified Data.Text as T
26+import qualified Data.Text.Lazy as LT
27+import qualified Data.UUID.Types as UUID
28+import PropUtils
29+import PropertyRTFunctors
30+
31+
32+roundTripTests :: TestTree
33+roundTripTests =
34+ testGroup "roundTrip" [
35+ testProperty "Bool" $ roundTripEq True
36+ , testProperty "Double" $ roundTripEq (1 :: Approx Double)
37+ , testProperty "Int" $ roundTripEq (1 :: Int)
38+ , testProperty "NonEmpty Char" $ roundTripEq (undefined :: NonEmpty Char)
39+ , testProperty "Integer" $ roundTripEq (1 :: Integer)
40+ , testProperty "String" $ roundTripEq ("" :: String)
41+ , testProperty "Text" $ roundTripEq T.empty
42+ , testProperty "Lazy Text" $ roundTripEq LT.empty
43+ , testProperty "Foo" $ roundTripEq (undefined :: Foo)
44+ , testProperty "Day" $ roundTripEq (undefined :: Day)
45+ , testProperty "BCE Day" $ roundTripEq (undefined :: BCEDay)
46+ , testProperty "DotNetTime" $ roundTripEq (undefined :: Approx DotNetTime)
47+ , testProperty "LocalTime" $ roundTripEq (undefined :: LocalTime)
48+ , testProperty "TimeOfDay" $ roundTripEq (undefined :: TimeOfDay)
49+ , testProperty "UTCTime" $ roundTripEq (undefined :: UTCTime)
50+ , testProperty "ZonedTime" $ roundTripEq (undefined :: ZonedTime)
51+ , testProperty "NominalDiffTime" $ roundTripEq (undefined :: NominalDiffTime)
52+ , testProperty "DiffTime" $ roundTripEq (undefined :: DiffTime)
53+ , testProperty "DayOfWeek" $ roundTripEq (undefined :: DayOfWeek)
54+ , testProperty "SystemTime" $ roundTripEq (undefined :: SystemTime)
55+ , testProperty "CalendarDiffTime" $ roundTripEq (undefined :: CalendarDiffTime)
56+ , testProperty "CalendarDiffDays" $ roundTripEq (undefined :: CalendarDiffDays)
57+ , testProperty "Version" $ roundTripEq (undefined :: Version)
58+ , testProperty "Natural" $ roundTripEq (undefined :: Natural)
59+ , testProperty "Proxy" $ roundTripEq (undefined :: Proxy Int)
60+ , testProperty "Tagged" $ roundTripEq (undefined :: Tagged Int Char)
61+ , testProperty "Const" $ roundTripEq (undefined :: Const Int Char)
62+ , testProperty "DList" $ roundTripEq (undefined :: DList Int)
63+ , testProperty "Seq" $ roundTripEq (undefined :: Seq Int)
64+ , testProperty "Rational" $ roundTripEq (undefined :: Rational)
65+ , testProperty "Ratio Int" $ roundTripEq (undefined :: Ratio Int)
66+ , testProperty "UUID" $ roundTripEq UUID.nil
67+ , roundTripFunctorsTests
68+ , testGroup "ghcGenerics" [
69+ testProperty "OneConstructor" $ roundTripEq OneConstructor
70+ , testProperty "Product2" $ roundTripEq (undefined :: Product2 Int Bool)
71+ , testProperty "Product6" $ roundTripEq (undefined :: P6)
72+ , testProperty "Sum4" $ roundTripEq (undefined :: S4)
73+ ]
74+ ]
tests/PropertyTH.hsnew file+130
@@ -0,0 +1,130 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
3+module PropertyTH ( templateHaskellTests ) where
4+
5+import Prelude.Compat
6+
7+import Data.Semigroup (Option(..))
8+import Encoders
9+import Instances ()
10+import Test.Tasty (TestTree, testGroup)
11+import Test.Tasty.QuickCheck (testProperty)
12+import Test.QuickCheck ( (===) )
13+import Types
14+import PropUtils
15+
16+
17+templateHaskellTests :: TestTree
18+templateHaskellTests =
19+ testGroup "template-haskell" [
20+ testGroup "toJSON" [
21+ testGroup "Nullary" [
22+ testProperty "string" (isString . thNullaryToJSONString)
23+ , testProperty "2ElemArray" (is2ElemArray . thNullaryToJSON2ElemArray)
24+ , testProperty "TaggedObject" (isNullaryTaggedObject . thNullaryToJSONTaggedObject)
25+ , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thNullaryToJSONObjectWithSingleField)
26+
27+ , testGroup "roundTrip" [
28+ testProperty "string" (toParseJSON thNullaryParseJSONString thNullaryToJSONString)
29+ , testProperty "2ElemArray" (toParseJSON thNullaryParseJSON2ElemArray thNullaryToJSON2ElemArray)
30+ , testProperty "TaggedObject" (toParseJSON thNullaryParseJSONTaggedObject thNullaryToJSONTaggedObject)
31+ , testProperty "ObjectWithSingleField" (toParseJSON thNullaryParseJSONObjectWithSingleField thNullaryToJSONObjectWithSingleField)
32+ ]
33+ ]
34+ , testGroup "EitherTextInt" [
35+ testProperty "UntaggedValue" (isUntaggedValueETI . thEitherTextIntToJSONUntaggedValue)
36+ , testProperty "roundtrip" (toParseJSON thEitherTextIntParseJSONUntaggedValue thEitherTextIntToJSONUntaggedValue)
37+ ]
38+ , testGroup "SomeType" [
39+ testProperty "2ElemArray" (is2ElemArray . thSomeTypeToJSON2ElemArray)
40+ , testProperty "TaggedObject" (isTaggedObject . thSomeTypeToJSONTaggedObject)
41+ , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thSomeTypeToJSONObjectWithSingleField)
42+ , testGroup "roundTrip" [
43+ testProperty "2ElemArray" (toParseJSON thSomeTypeParseJSON2ElemArray thSomeTypeToJSON2ElemArray)
44+ , testProperty "TaggedObject" (toParseJSON thSomeTypeParseJSONTaggedObject thSomeTypeToJSONTaggedObject)
45+ , testProperty "ObjectWithSingleField" (toParseJSON thSomeTypeParseJSONObjectWithSingleField thSomeTypeToJSONObjectWithSingleField)
46+
47+ , testProperty "2ElemArray unary" (toParseJSON1 thSomeTypeLiftParseJSON2ElemArray thSomeTypeLiftToJSON2ElemArray)
48+ , testProperty "TaggedObject unary" (toParseJSON1 thSomeTypeLiftParseJSONTaggedObject thSomeTypeLiftToJSONTaggedObject)
49+ , testProperty "ObjectWithSingleField unary" (toParseJSON1 thSomeTypeLiftParseJSONObjectWithSingleField thSomeTypeLiftToJSONObjectWithSingleField)
50+
51+ ]
52+ ]
53+ , testGroup "Approx" [
54+ testProperty "string" (isString . thApproxToJSONUnwrap)
55+ , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thApproxToJSONDefault)
56+ , testGroup "roundTrip" [
57+ testProperty "string" (toParseJSON thApproxParseJSONUnwrap thApproxToJSONUnwrap)
58+ , testProperty "ObjectWithSingleField" (toParseJSON thApproxParseJSONDefault thApproxToJSONDefault)
59+ ]
60+ ]
61+ , testGroup "GADT" [
62+ testProperty "string" (isString . thGADTToJSONUnwrap)
63+ , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thGADTToJSONDefault)
64+ , testGroup "roundTrip" [
65+ testProperty "string" (toParseJSON thGADTParseJSONUnwrap thGADTToJSONUnwrap)
66+ , testProperty "ObjectWithSingleField" (toParseJSON thGADTParseJSONDefault thGADTToJSONDefault)
67+ ]
68+ ]
69+ , testGroup "OneConstructor" [
70+ testProperty "default" (isEmptyArray . thOneConstructorToJSONDefault)
71+ , testProperty "Tagged" (isTaggedObject . thOneConstructorToJSONTagged)
72+ , testGroup "roundTrip" [
73+ testProperty "default" (toParseJSON thOneConstructorParseJSONDefault thOneConstructorToJSONDefault)
74+ , testProperty "Tagged" (toParseJSON thOneConstructorParseJSONTagged thOneConstructorToJSONTagged)
75+ ]
76+ ]
77+ , testGroup "OptionField" [
78+ testProperty "like Maybe" $
79+ \x -> thOptionFieldToJSON (OptionField (Option x)) === thMaybeFieldToJSON (MaybeField x)
80+ , testProperty "roundTrip" (toParseJSON thOptionFieldParseJSON thOptionFieldToJSON)
81+ ]
82+ ]
83+ , testGroup "toEncoding" [
84+ testProperty "NullaryString" $
85+ thNullaryToJSONString `sameAs` thNullaryToEncodingString
86+ , testProperty "Nullary2ElemArray" $
87+ thNullaryToJSON2ElemArray `sameAs` thNullaryToEncoding2ElemArray
88+ , testProperty "NullaryTaggedObject" $
89+ thNullaryToJSONTaggedObject `sameAs` thNullaryToEncodingTaggedObject
90+ , testProperty "NullaryObjectWithSingleField" $
91+ thNullaryToJSONObjectWithSingleField `sameAs`
92+ thNullaryToEncodingObjectWithSingleField
93+ , testProperty "ApproxUnwrap" $
94+ thApproxToJSONUnwrap `sameAs` thApproxToEncodingUnwrap
95+ , testProperty "ApproxDefault" $
96+ thApproxToJSONDefault `sameAs` thApproxToEncodingDefault
97+
98+ , testProperty "EitherTextInt UntaggedValue" $
99+ thEitherTextIntToJSONUntaggedValue `sameAs` thEitherTextIntToEncodingUntaggedValue
100+
101+ , testProperty "SomeType2ElemArray" $
102+ thSomeTypeToJSON2ElemArray `sameAs` thSomeTypeToEncoding2ElemArray
103+ , testProperty "SomeType2ElemArray unary" $
104+ thSomeTypeLiftToJSON2ElemArray `sameAs1` thSomeTypeLiftToEncoding2ElemArray
105+ , testProperty "SomeType2ElemArray unary agree" $
106+ thSomeTypeToEncoding2ElemArray `sameAs1Agree` thSomeTypeLiftToEncoding2ElemArray
107+
108+ , testProperty "SomeTypeTaggedObject" $
109+ thSomeTypeToJSONTaggedObject `sameAs` thSomeTypeToEncodingTaggedObject
110+ , testProperty "SomeTypeTaggedObject unary" $
111+ thSomeTypeLiftToJSONTaggedObject `sameAs1` thSomeTypeLiftToEncodingTaggedObject
112+ , testProperty "SomeTypeTaggedObject unary agree" $
113+ thSomeTypeToEncodingTaggedObject `sameAs1Agree` thSomeTypeLiftToEncodingTaggedObject
114+
115+ , testProperty "SomeTypeObjectWithSingleField" $
116+ thSomeTypeToJSONObjectWithSingleField `sameAs` thSomeTypeToEncodingObjectWithSingleField
117+ , testProperty "SomeTypeObjectWithSingleField unary" $
118+ thSomeTypeLiftToJSONObjectWithSingleField `sameAs1` thSomeTypeLiftToEncodingObjectWithSingleField
119+ , testProperty "SomeTypeObjectWithSingleField unary agree" $
120+ thSomeTypeToEncodingObjectWithSingleField `sameAs1Agree` thSomeTypeLiftToEncodingObjectWithSingleField
121+
122+ , testProperty "OneConstructorDefault" $
123+ thOneConstructorToJSONDefault `sameAs` thOneConstructorToEncodingDefault
124+ , testProperty "OneConstructorTagged" $
125+ thOneConstructorToJSONTagged `sameAs` thOneConstructorToEncodingTagged
126+
127+ , testProperty "OptionField" $
128+ thOptionFieldToJSON `sameAs` thOptionFieldToEncoding
129+ ]
130+ ]
tests/SerializationFormatSpec.hs+53−30
@@ -1,10 +1,9 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE GADTs #-}
3{-# LANGUAGE GADTs #-}
4+{-# LANGUAGE NoImplicitPrelude #-}
4{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE OverloadedStrings #-}
5−#if __GLASGOW_HASKELL__ >= 708
6{-# LANGUAGE DataKinds #-}
6{-# LANGUAGE DataKinds #-}
7−#endif
8
7
9------------------------------------------------------------------------------
8------------------------------------------------------------------------------
10-- These tests assert that the JSON serialization doesn't change by accident.
9-- These tests assert that the JSON serialization doesn't change by accident.
@@ -15,7 +14,6 @@ module SerializationFormatSpec
15 tests
14 tests
16 ) where
15 ) where
17
16
18−import Prelude ()
19import Prelude.Compat
17import Prelude.Compat
20
18
21import Control.Applicative (Const(..))
19import Control.Applicative (Const(..))
@@ -32,12 +30,14 @@ import Data.Proxy (Proxy(..))
32import Data.Scientific (Scientific)
30import Data.Scientific (Scientific)
33import Data.Tagged (Tagged(..))
31import Data.Tagged (Tagged(..))
34import Data.Time (fromGregorian)
32import Data.Time (fromGregorian)
33+import Data.Time.Calendar.Compat (CalendarDiffDays (..), DayOfWeek (..))
34+import Data.Time.LocalTime.Compat (CalendarDiffTime (..))
35+import Data.Time.Clock.System.Compat (SystemTime (..))
35import Data.Word (Word8)
36import Data.Word (Word8)
36import GHC.Generics (Generic)
37import GHC.Generics (Generic)
37import Instances ()
38import Instances ()
38−import Test.Framework (Test, testGroup)
39+import Test.Tasty (TestTree, testGroup)
39−import Test.Framework.Providers.HUnit (testCase)
40−import Test.HUnit (assertFailure, assertEqual)
40+import Test.Tasty.HUnit (assertFailure, assertEqual, testCase)
41import Types (Approx(..), Compose3, Compose3', I)
41import Types (Approx(..), Compose3, Compose3', I)
42import qualified Data.ByteString.Lazy.Char8 as L
42import qualified Data.ByteString.Lazy.Char8 as L
43import qualified Data.DList as DList
43import qualified Data.DList as DList
@@ -54,7 +54,7 @@ import qualified Data.Tree as Tree
54import qualified Data.UUID.Types as UUID
54import qualified Data.UUID.Types as UUID
55import qualified Data.Vector as Vector
55import qualified Data.Vector as Vector
56
56
57−tests :: [Test]
57+tests :: [TestTree]
58tests =
58tests =
59 [
59 [
60 testGroup "To JSON representation" $ fmap assertJsonEncodingExample jsonEncodingExamples
60 testGroup "To JSON representation" $ fmap assertJsonEncodingExample jsonEncodingExamples
@@ -69,13 +69,14 @@ jsonExamples =
69 example "Either Left" "{\"Left\":1}" (Left 1 :: Either Int Int)
69 example "Either Left" "{\"Left\":1}" (Left 1 :: Either Int Int)
70 , example "Either Right" "{\"Right\":1}" (Right 1 :: Either Int Int)
70 , example "Either Right" "{\"Right\":1}" (Right 1 :: Either Int Int)
71 , example "Nothing" "null" (Nothing :: Maybe Int)
71 , example "Nothing" "null" (Nothing :: Maybe Int)
72+ -- Maybe serialising is lossy
73+ -- https://github.com/bos/aeson/issues/376
74+ , Example "Just Nothing" ["null"] (Just Nothing :: Maybe (Maybe Int)) Nothing
72 , example "Just" "1" (Just 1 :: Maybe Int)
75 , example "Just" "1" (Just 1 :: Maybe Int)
73 , example "Proxy Int" "null" (Proxy :: Proxy Int)
76 , example "Proxy Int" "null" (Proxy :: Proxy Int)
74 , example "Tagged Char Int" "1" (Tagged 1 :: Tagged Char Int)
77 , example "Tagged Char Int" "1" (Tagged 1 :: Tagged Char Int)
75−#if __GLASGOW_HASKELL__ >= 708
76 -- Test Tagged instance is polykinded
78 -- Test Tagged instance is polykinded
77 , example "Tagged 123 Int" "1" (Tagged 1 :: Tagged 123 Int)
79 , example "Tagged 123 Int" "1" (Tagged 1 :: Tagged 123 Int)
78−#endif
79 , example "Const Char Int" "\"c\"" (Const 'c' :: Const Char Int)
80 , example "Const Char Int" "\"c\"" (Const 'c' :: Const Char Int)
80 , example "Tuple" "[1,2]" ((1, 2) :: (Int, Int))
81 , example "Tuple" "[1,2]" ((1, 2) :: (Int, Int))
81 , example "NonEmpty" "[1,2,3]" (1 :| [2, 3] :: NonEmpty Int)
82 , example "NonEmpty" "[1,2,3]" (1 :| [2, 3] :: NonEmpty Int)
@@ -83,22 +84,22 @@ jsonExamples =
83 , example "DList" "[1,2,3]" (DList.fromList [1, 2, 3] :: DList.DList Int)
84 , example "DList" "[1,2,3]" (DList.fromList [1, 2, 3] :: DList.DList Int)
84 , example "()" "[]" ()
85 , example "()" "[]" ()
85
86
86− , Example "HashMap Int Int"
87+ , ndExample "HashMap Int Int"
87 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
88 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
88 (HM.fromList [(0,1),(2,3)] :: HM.HashMap Int Int)
89 (HM.fromList [(0,1),(2,3)] :: HM.HashMap Int Int)
89− , Example "Map Int Int"
90+ , ndExample "Map Int Int"
90 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
91 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
91 (M.fromList [(0,1),(2,3)] :: M.Map Int Int)
92 (M.fromList [(0,1),(2,3)] :: M.Map Int Int)
92− , Example "Map (Tagged Int Int) Int"
93+ , ndExample "Map (Tagged Int Int) Int"
93 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
94 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
94 (M.fromList [(Tagged 0,1),(Tagged 2,3)] :: M.Map (Tagged Int Int) Int)
95 (M.fromList [(Tagged 0,1),(Tagged 2,3)] :: M.Map (Tagged Int Int) Int)
95 , example "Map [Int] Int"
96 , example "Map [Int] Int"
96 "[[[0],1],[[2],3]]"
97 "[[[0],1],[[2],3]]"
97 (M.fromList [([0],1),([2],3)] :: M.Map [Int] Int)
98 (M.fromList [([0],1),([2],3)] :: M.Map [Int] Int)
98− , Example "Map [Char] Int"
99+ , ndExample "Map [Char] Int"
99 [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]
100 [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]
100 (M.fromList [("ab",1),("cd",3)] :: M.Map String Int)
101 (M.fromList [("ab",1),("cd",3)] :: M.Map String Int)
101− , Example "Map [I Char] Int"
102+ , ndExample "Map [I Char] Int"
102 [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]
103 [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]
103 (M.fromList [(map pure "ab",1),(map pure "cd",3)] :: M.Map [I Char] Int)
104 (M.fromList [(map pure "ab",1),(map pure "cd",3)] :: M.Map [I Char] Int)
104
105
@@ -194,16 +195,27 @@ jsonExamples =
194 , example "Semigroup.WrappedMonoid Int" "2" (Semigroup.WrapMonoid 2 :: Semigroup.WrappedMonoid Int)
195 , example "Semigroup.WrappedMonoid Int" "2" (Semigroup.WrapMonoid 2 :: Semigroup.WrappedMonoid Int)
195 , example "Semigroup.Option Just" "2" (pure 2 :: Semigroup.Option Int)
196 , example "Semigroup.Option Just" "2" (pure 2 :: Semigroup.Option Int)
196 , example "Semigroup.Option Nothing" "null" (Semigroup.Option (Nothing :: Maybe Bool))
197 , example "Semigroup.Option Nothing" "null" (Semigroup.Option (Nothing :: Maybe Bool))
198+
199+ -- time 1.9
200+ , example "SystemTime" "123.123456789" (MkSystemTime 123 123456789)
201+ , Example "SystemTime" ["124.23456789"]
202+ (MkSystemTime 123 1234567890)
203+ (MkSystemTime 124 234567890)
204+ , ndExample "CalendarDiffTime"
205+ [ "{\"months\":12,\"time\":456.789}", "{\"time\":456.789,\"months\":12}" ]
206+ (CalendarDiffTime 12 456.789)
207+ , ndExample "CalendarDiffDays"
208+ [ "{\"months\":12,\"days\":20}", "{\"days\":20,\"months\":12}" ]
209+ (CalendarDiffDays 12 20)
210+ , example "DayOfWeek" "\"monday\"" Monday
197 ]
211 ]
198
212
199jsonEncodingExamples :: [Example]
213jsonEncodingExamples :: [Example]
200jsonEncodingExamples =
214jsonEncodingExamples =
201 [
215 [
202− -- Maybe serialising is lossy
203− -- https://github.com/bos/aeson/issues/376
204− example "Just Nothing" "null" (Just Nothing :: Maybe (Maybe Int))
205 -- infinities cannot be recovered, null is decoded as NaN
216 -- infinities cannot be recovered, null is decoded as NaN
217+ example "inf :: Double" "null" (Approx $ 1/0 :: Approx Double)
206− , example "inf :: Double" "null" (Approx $ 1/0 :: Approx Double)
218+ , example "-inf :: Double" "null" (Approx $ -1/0 :: Approx Double)
207 ]
219 ]
208
220
209jsonDecodingExamples :: [Example]
221jsonDecodingExamples :: [Example]
@@ -225,16 +237,27 @@ jsonDecodingExamples = [
225 ]
237 ]
226
238
227data Example where
239data Example where
228− Example
240+ Example
229− :: (Eq a, Show a, ToJSON a, FromJSON a)
241+ :: (Eq a, Show a, ToJSON a, FromJSON a)
242+ => String -- name
243+ -> [L.ByteString] -- encoded variants
244+ -> a -- input
245+ -> a -- decoded
230− => String -> [L.ByteString] -> a -> Example -- empty bytestring will fail, any p [] == False
246+ -> Example -- empty bytestring will fail, any p [] == False
247+
231− MaybeExample
248+ MaybeExample
232− :: (Eq a, Show a, FromJSON a)
249+ :: (Eq a, Show a, FromJSON a)
233− => String -> L.ByteString -> Maybe a -> Example
250+ => String -> L.ByteString -> Maybe a -> Example
234
251
235example :: (Eq a, Show a, ToJSON a, FromJSON a)
252example :: (Eq a, Show a, ToJSON a, FromJSON a)
236 => String -> L.ByteString -> a -> Example
253 => String -> L.ByteString -> a -> Example
237−example n bs x = Example n [bs] x
254+example n bs x = Example n [bs] x x
255+
256+-- | Non-deterministic example, input encodes to some of bytestrings.
257+ndExample :: (Eq a, Show a, ToJSON a, FromJSON a)
258+ => String -> [L.ByteString] -> a -> Example
259+ndExample n bss x = Example n bss x x
260+
238
261
239data MyEither a b = MyLeft a | MyRight b
262data MyEither a b = MyLeft a | MyRight b
240 deriving (Generic, Show, Eq)
263 deriving (Generic, Show, Eq)
@@ -246,17 +269,17 @@ instance (ToJSON a, ToJSON b) => ToJSON (MyEither a b) where
246instance (FromJSON a, FromJSON b) => FromJSON (MyEither a b) where
269instance (FromJSON a, FromJSON b) => FromJSON (MyEither a b) where
247 parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }
270 parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }
248
271
249−assertJsonExample :: Example -> Test
272+assertJsonExample :: Example -> TestTree
250−assertJsonExample (Example name bss val) = testCase name $ do
273+assertJsonExample (Example name bss val val') = testCase name $ do
251 assertSomeEqual "encode" bss (encode val)
274 assertSomeEqual "encode" bss (encode val)
252 assertSomeEqual "encode/via value" bss (encode $ toJSON val)
275 assertSomeEqual "encode/via value" bss (encode $ toJSON val)
253 for_ bss $ \bs ->
276 for_ bss $ \bs ->
254− assertEqual "decode" (Just val) (decode bs)
277+ assertEqual "decode" (Just val') (decode bs)
255assertJsonExample (MaybeExample name bs mval) = testCase name $
278assertJsonExample (MaybeExample name bs mval) = testCase name $
256 assertEqual "decode" mval (decode bs)
279 assertEqual "decode" mval (decode bs)
257
280
258−assertJsonEncodingExample :: Example -> Test
281+assertJsonEncodingExample :: Example -> TestTree
259−assertJsonEncodingExample (Example name bss val) = testCase name $ do
282+assertJsonEncodingExample (Example name bss val _) = testCase name $ do
260 assertSomeEqual "encode" bss (encode val)
283 assertSomeEqual "encode" bss (encode val)
261 assertSomeEqual "encode/via value" bss (encode $ toJSON val)
284 assertSomeEqual "encode/via value" bss (encode $ toJSON val)
262assertJsonEncodingExample (MaybeExample name _ _) = testCase name $
285assertJsonEncodingExample (MaybeExample name _ _) = testCase name $
tests/Tests.hs+5−3
@@ -1,9 +1,10 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
1module Main (main) where
3module Main (main) where
2
4
3−import Prelude ()
4import Prelude.Compat
5import Prelude.Compat
5
6
6−import Test.Framework (defaultMain)
7+import Test.Tasty (defaultMain, testGroup)
7import qualified DataFamilies.Properties as DF
8import qualified DataFamilies.Properties as DF
8import qualified Properties
9import qualified Properties
9import qualified UnitTests
10import qualified UnitTests
@@ -11,4 +12,5 @@ import qualified UnitTests
11main :: IO ()
12main :: IO ()
12main = do
13main = do
13 ioTests <- UnitTests.ioTests
14 ioTests <- UnitTests.ioTests
14− defaultMain (DF.tests : Properties.tests : UnitTests.tests : ioTests)
15+ let allTests = DF.tests : Properties.tests : UnitTests.tests : ioTests
16+ defaultMain (testGroup "tests" allTests)
tests/Types.hs+10−5
@@ -4,19 +4,21 @@
4{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GADTs #-}
5{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7−{-# LANGUAGE StandaloneDeriving #-}
7+{-# LANGUAGE NoImplicitPrelude #-}
8{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE ScopedTypeVariables #-}
9+{-# LANGUAGE StandaloneDeriving #-}
9
10
10module Types (module Types) where
11module Types (module Types) where
11
12
12−import Prelude ()
13import Prelude.Compat
13import Prelude.Compat
14
14
15import Math.NumberTheory.Logarithms (intLog2)
15import Math.NumberTheory.Logarithms (intLog2)
16+import Control.Applicative ((<$>))
16import Data.Data
17import Data.Data
17import Data.Functor.Compose (Compose (..))
18import Data.Functor.Compose (Compose (..))
18import Data.Functor.Identity (Identity (..))
19import Data.Functor.Identity (Identity (..))
19import Data.Hashable (Hashable (..))
20import Data.Hashable (Hashable (..))
21+import Data.Semigroup (Option)
20import Data.Text
22import Data.Text
21import Data.Time (Day (..), fromGregorian)
23import Data.Time (Day (..), fromGregorian)
22import GHC.Generics
24import GHC.Generics
@@ -104,6 +106,10 @@ deriving instance Data (GADT String)
104deriving instance Eq (GADT a)
106deriving instance Eq (GADT a)
105deriving instance Show (GADT a)
107deriving instance Show (GADT a)
106
108
109+newtype MaybeField = MaybeField { maybeField :: Maybe Int }
110+newtype OptionField = OptionField { optionField :: Option Int }
111+ deriving (Eq, Show)
112+
107deriving instance Generic Foo
113deriving instance Generic Foo
108deriving instance Generic UFoo
114deriving instance Generic UFoo
109deriving instance Generic OneConstructor
115deriving instance Generic OneConstructor
@@ -113,9 +119,8 @@ deriving instance Generic (Sum4 a b c d)
113deriving instance Generic (Approx a)
119deriving instance Generic (Approx a)
114deriving instance Generic Nullary
120deriving instance Generic Nullary
115deriving instance Generic (SomeType a)
121deriving instance Generic (SomeType a)
116−#if __GLASGOW_HASKELL__ >= 706
117deriving instance Generic1 SomeType
122deriving instance Generic1 SomeType
118−#endif
123+deriving instance Generic OptionField
119deriving instance Generic EitherTextInt
124deriving instance Generic EitherTextInt
120
125
121failure :: Show a => String -> String -> a -> Property
126failure :: Show a => String -> String -> a -> Property
@@ -146,7 +151,7 @@ instance Hashable a => Hashable (LogScaled a) where
146 hashWithSalt salt (LogScaled a) = hashWithSalt salt a
151 hashWithSalt salt (LogScaled a) = hashWithSalt salt a
147
152
148instance Arbitrary a => Arbitrary (LogScaled a) where
153instance Arbitrary a => Arbitrary (LogScaled a) where
149− arbitrary = fmap LogScaled $ scale (\x -> intLog2 $ x + 1) arbitrary
154+ arbitrary = LogScaled <$> scale (\x -> intLog2 $ x + 1) arbitrary
150 shrink = fmap LogScaled . shrink . getLogScaled
155 shrink = fmap LogScaled . shrink . getLogScaled
151
156
152instance ToJSON a => ToJSON (LogScaled a) where
157instance ToJSON a => ToJSON (LogScaled a) where
tests/UnitTests.hs+276−39
@@ -1,55 +1,72 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE NoImplicitPrelude #-}
4{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6{-# LANGUAGE ScopedTypeVariables #-}
6{-# LANGUAGE TemplateHaskell #-}
7{-# LANGUAGE TemplateHaskell #-}
8+{-# LANGUAGE QuasiQuotes #-}
7
9
8-- For Data.Aeson.Types.camelTo
10-- For Data.Aeson.Types.camelTo
9{-# OPTIONS_GHC -fno-warn-deprecations #-}
11{-# OPTIONS_GHC -fno-warn-deprecations #-}
10
12
13+#if MIN_VERSION_base(4,9,0)
14+{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
15+#endif
16+
11module UnitTests
17module UnitTests
12 (
18 (
13 ioTests
19 ioTests
14 , tests
20 , tests
21+ , withEmbeddedJSONTest
15 ) where
22 ) where
16
23
17−import Prelude ()
18import Prelude.Compat
24import Prelude.Compat
19
25
20import Control.Monad (forM, forM_)
26import Control.Monad (forM, forM_)
21−import Data.Aeson ((.=), (.:), (.:?), (.:!), FromJSON(..), FromJSONKeyFunction(..), FromJSONKey(..), ToJSON1(..), decode, eitherDecode, encode, fromJSON, genericParseJSON, genericToEncoding, genericToJSON, object, withObject)
27+import Data.Aeson ((.=), (.:), (.:?), (.:!), FromJSON(..), FromJSONKeyFunction(..), FromJSONKey(..), ToJSON1(..), decode, eitherDecode, encode, fromJSON, genericParseJSON, genericToEncoding, genericToJSON, object, withObject, withEmbeddedJSON)
22import Data.Aeson.Internal (JSONPathElement(..), formatError)
28import Data.Aeson.Internal (JSONPathElement(..), formatError)
29+import Data.Aeson.QQ.Simple (aesonQQ)
23import Data.Aeson.TH (deriveJSON, deriveToJSON, deriveToJSON1)
30import Data.Aeson.TH (deriveJSON, deriveToJSON, deriveToJSON1)
24import Data.Aeson.Text (encodeToTextBuilder)
31import Data.Aeson.Text (encodeToTextBuilder)
32+import Data.Aeson.Parser
33+ ( json, jsonLast, jsonAccum, jsonNoDup
34+ , json', jsonLast', jsonAccum', jsonNoDup')
35+import Data.Aeson.Types
36+ ( Options(..), Result(Success, Error), ToJSON(..)
37+ , Value(Array, Bool, Null, Number, Object, String), camelTo, camelTo2
25−import Data.Aeson.Types (Options(..), Result(Success), ToJSON(..), Value(Null), camelTo, camelTo2, defaultOptions, omitNothingFields, parse)
38+ , defaultOptions, formatPath, formatRelativePath, omitNothingFields, parse)
39+import Data.Attoparsec.ByteString (Parser, parseOnly)
26import Data.Char (toUpper)
40import Data.Char (toUpper)
27import Data.Either.Compat (isLeft, isRight)
41import Data.Either.Compat (isLeft, isRight)
28import Data.Hashable (hash)
42import Data.Hashable (hash)
29−import Data.List (sort)
43+import Data.HashMap.Strict (HashMap)
44+import Data.List (sort, isSuffixOf)
30import Data.Maybe (fromMaybe)
45import Data.Maybe (fromMaybe)
31−import Data.Sequence (Seq)
46+import Data.Scientific (Scientific, scientific)
32import Data.Tagged (Tagged(..))
47import Data.Tagged (Tagged(..))
33import Data.Text (Text)
48import Data.Text (Text)
34import Data.Time (UTCTime)
49import Data.Time (UTCTime)
35−import Data.Time.Format (parseTime)
50+import Data.Time.Format.Compat (parseTimeM, defaultTimeLocale)
36−import Data.Time.Locale.Compat (defaultTimeLocale)
37import GHC.Generics (Generic)
51import GHC.Generics (Generic)
38import Instances ()
52import Instances ()
53+import Numeric.Natural (Natural)
39import System.Directory (getDirectoryContents)
54import System.Directory (getDirectoryContents)
40import System.FilePath ((</>), takeExtension, takeFileName)
55import System.FilePath ((</>), takeExtension, takeFileName)
41−import Test.Framework (Test, testGroup)
56+import Test.Tasty (TestTree, testGroup)
42−import Test.Framework.Providers.HUnit (testCase)
43−import Test.HUnit (Assertion, assertBool, assertFailure, assertEqual)
57+import Test.Tasty.HUnit (Assertion, assertBool, assertFailure, assertEqual, testCase, (@?=))
44import Text.Printf (printf)
58import Text.Printf (printf)
45import UnitTests.NullaryConstructors (nullaryConstructors)
59import UnitTests.NullaryConstructors (nullaryConstructors)
60+import qualified Data.ByteString as S
46import qualified Data.ByteString.Base16.Lazy as LBase16
61import qualified Data.ByteString.Base16.Lazy as LBase16
47import qualified Data.ByteString.Lazy.Char8 as L
62import qualified Data.ByteString.Lazy.Char8 as L
48import qualified Data.HashSet as HashSet
63import qualified Data.HashSet as HashSet
64+import qualified Data.HashMap.Lazy as HashMap
49import qualified Data.Text.Lazy as LT
65import qualified Data.Text.Lazy as LT
50import qualified Data.Text.Lazy.Builder as TLB
66import qualified Data.Text.Lazy.Builder as TLB
51import qualified Data.Text.Lazy.Encoding as LT
67import qualified Data.Text.Lazy.Encoding as LT
52import qualified Data.Text.Lazy.Encoding as TLE
68import qualified Data.Text.Lazy.Encoding as TLE
69+import qualified Data.Vector as Vector
53import qualified ErrorMessages
70import qualified ErrorMessages
54import qualified SerializationFormatSpec
71import qualified SerializationFormatSpec
55
72
@@ -57,7 +74,7 @@ import qualified SerializationFormatSpec
57import Data.Aeson.Parser.UnescapeFFI ()
74import Data.Aeson.Parser.UnescapeFFI ()
58import Data.Aeson.Parser.UnescapePure ()
75import Data.Aeson.Parser.UnescapePure ()
59
76
60−tests :: Test
77+tests :: TestTree
61tests = testGroup "unit" [
78tests = testGroup "unit" [
62 testGroup "SerializationFormatSpec" SerializationFormatSpec.tests
79 testGroup "SerializationFormatSpec" SerializationFormatSpec.tests
63 , testGroup "ErrorMessages" ErrorMessages.tests
80 , testGroup "ErrorMessages" ErrorMessages.tests
@@ -84,8 +101,8 @@ tests = testGroup "unit" [
84 testCase "example 1" formatErrorExample
101 testCase "example 1" formatErrorExample
85 ]
102 ]
86 , testGroup ".:, .:?, .:!" $ fmap (testCase "-") dotColonMark
103 , testGroup ".:, .:?, .:!" $ fmap (testCase "-") dotColonMark
87− , testGroup "JSONPath" $ fmap (testCase "-") jsonPath
88 , testGroup "Hashable laws" $ fmap (testCase "-") hashableLaws
104 , testGroup "Hashable laws" $ fmap (testCase "-") hashableLaws
105+ , testGroup "Object construction" $ fmap (testCase "-") objectConstruction
89 , testGroup "Issue #351" $ fmap (testCase "-") issue351
106 , testGroup "Issue #351" $ fmap (testCase "-") issue351
90 , testGroup "Nullary constructors" $ fmap (testCase "-") nullaryConstructors
107 , testGroup "Nullary constructors" $ fmap (testCase "-") nullaryConstructors
91 , testGroup "FromJSONKey" $ fmap (testCase "-") fromJSONKeyAssertions
108 , testGroup "FromJSONKey" $ fmap (testCase "-") fromJSONKeyAssertions
@@ -93,6 +110,22 @@ tests = testGroup "unit" [
93 , testCase "Unescape string (PR #477)" unescapeString
110 , testCase "Unescape string (PR #477)" unescapeString
94 , testCase "Show Options" showOptions
111 , testCase "Show Options" showOptions
95 , testGroup "SingleMaybeField" singleMaybeField
112 , testGroup "SingleMaybeField" singleMaybeField
113+ , testCase "withEmbeddedJSON" withEmbeddedJSONTest
114+ , testCase "SingleFieldCon" singleFieldCon
115+ , testGroup "UnknownFields" unknownFields
116+ , testGroup "Ordering of object keys" keyOrdering
117+ , testCase "Ratio with denominator 0" ratioDenominator0
118+ , testCase "Rational parses number" rationalNumber
119+ , testCase "Big rational" bigRationalDecoding
120+ , testCase "Small rational" smallRationalDecoding
121+ , testCase "Big scientific exponent" bigScientificExponent
122+ , testCase "Big integer decoding" bigIntegerDecoding
123+ , testCase "Big natural decading" bigNaturalDecoding
124+ , testCase "Big integer key decoding" bigIntegerKeyDecoding
125+ , testGroup "QQ.Simple"
126+ [ testCase "example" $
127+ assertEqual "" (object ["foo" .= True]) [aesonQQ| {"foo": true } |]
128+ ]
96 ]
129 ]
97
130
98roundTripCamel :: String -> Assertion
131roundTripCamel :: String -> Assertion
@@ -185,10 +218,18 @@ utcTimeGood = do
185 assertEqual "utctime" (parseWithRead "%F %H:%MZ" ts11) t11
218 assertEqual "utctime" (parseWithRead "%F %H:%MZ" ts11) t11
186 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-01T14:30:00Z") t12
219 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-01T14:30:00Z") t12
187
220
221+ -- leap seconds are included correctly
222+ let ts13 = "2015-08-23T23:59:60.128+00" :: LT.Text
223+ let (Just (t13 :: UTCTime)) = parseWithAeson ts13
224+ assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-08-23T23:59:60.128Z") t13
225+ let ts14 = "2015-08-23T23:59:60.999999999999+00" :: LT.Text
226+ let (Just (t14 :: UTCTime)) = parseWithAeson ts14
227+ assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-08-23T23:59:60.999999999999Z") t14
228+
188 where
229 where
189 parseWithRead :: String -> LT.Text -> UTCTime
230 parseWithRead :: String -> LT.Text -> UTCTime
190 parseWithRead f s =
231 parseWithRead f s =
191− fromMaybe (error "parseTime input malformed") . parseTime defaultTimeLocale f . LT.unpack $ s
232+ fromMaybe (error "parseTime input malformed") . parseTimeM True defaultTimeLocale f . LT.unpack $ s
192 parseWithAeson :: LT.Text -> Maybe UTCTime
233 parseWithAeson :: LT.Text -> Maybe UTCTime
193 parseWithAeson s = decode . LT.encodeUtf8 $ LT.concat ["\"", s, "\""]
234 parseWithAeson s = decode . LT.encodeUtf8 $ LT.concat ["\"", s, "\""]
194
235
@@ -204,6 +245,7 @@ utcTimeBad = do
204 verifyFailParse "2015-01-01T12:30:00.00+00:00Z" -- no Zulu if offset given
245 verifyFailParse "2015-01-01T12:30:00.00+00:00Z" -- no Zulu if offset given
205 verifyFailParse "2015-01-03 12:13:00.Z" -- decimal at the end but no digits
246 verifyFailParse "2015-01-03 12:13:00.Z" -- decimal at the end but no digits
206 verifyFailParse "2015-01-03 12:13.000Z" -- decimal at the end, but no seconds
247 verifyFailParse "2015-01-03 12:13.000Z" -- decimal at the end, but no seconds
248+ verifyFailParse "2015-01-03 23:59:61Z" -- exceeds allowed seconds per day
207 where
249 where
208 verifyFailParse (s :: LT.Text) =
250 verifyFailParse (s :: LT.Text) =
209 let (dec :: Maybe UTCTime) = decode . LT.encodeUtf8 $ LT.concat ["\"", s, "\""] in
251 let (dec :: Maybe UTCTime) = decode . LT.encodeUtf8 $ LT.concat ["\"", s, "\""] in
@@ -216,6 +258,18 @@ formatErrorExample =
216 lhs = "Error in $[0].foo.bar['a.b.c']['']['\\'\\\\'].end: error msg"
258 lhs = "Error in $[0].foo.bar['a.b.c']['']['\\'\\\\'].end: error msg"
217 in assertEqual "formatError example" lhs rhs
259 in assertEqual "formatError example" lhs rhs
218
260
261+formatPathExample :: Assertion
262+formatPathExample =
263+ let rhs = formatPath [Key "x", Index 0]
264+ lhs = "$.x[0]"
265+ in assertEqual "formatPath example" lhs rhs
266+
267+formatRelativePathExample :: Assertion
268+formatRelativePathExample =
269+ let rhs = formatPath [Key "x", Index 0]
270+ lhs = ".x[0]"
271+ in assertEqual "formatRelativePath example" lhs rhs
272+
219------------------------------------------------------------------------------
273------------------------------------------------------------------------------
220-- Comparison (.:?) and (.:!)
274-- Comparison (.:?) and (.:!)
221------------------------------------------------------------------------------
275------------------------------------------------------------------------------
@@ -246,27 +300,6 @@ dotColonMark = [
246 ex2 = "{\"value\": 42 }"
300 ex2 = "{\"value\": 42 }"
247 ex3 = "{\"value\": null }"
301 ex3 = "{\"value\": null }"
248
302
249−------------------------------------------------------------------------------
250−-- These tests check that JSONPath is tracked correctly
251−-----------------------------------------------------------------------------
252−
253−jsonPath :: [Assertion]
254−jsonPath = [
255− -- issue #356
256− assertEqual "Either"
257− (Left "Error in $[1].Left[1]: expected Bool, encountered Number")
258− (eitherDecode "[1,{\"Left\":[2,3]}]"
259− :: Either String (Int, Either (Int, Bool) ()))
260− -- issue #358
261− , assertEqual "Seq a"
262− (Left "Error in $[2]: expected Int, encountered Boolean")
263− (eitherDecode "[0,1,true]" :: Either String (Seq Int))
264− , assertEqual "Wibble"
265− (Left "Error in $.wibbleInt: expected Int, encountered Boolean")
266− (eitherDecode "{\"wibbleString\":\"\",\"wibbleInt\":true}"
267− :: Either String Wibble)
268− ]
269−
270------------------------------------------------------------------------------
303------------------------------------------------------------------------------
271-- Check that the hashes of two equal Value are the same
304-- Check that the hashes of two equal Value are the same
272------------------------------------------------------------------------------
305------------------------------------------------------------------------------
@@ -279,6 +312,18 @@ hashableLaws = [
279 a = object ["223" .= False, "807882556" .= True]
312 a = object ["223" .= False, "807882556" .= True]
280 b = object ["807882556" .= True, "223" .= False]
313 b = object ["807882556" .= True, "223" .= False]
281
314
315+------------------------------------------------------------------------------
316+-- Check that an alternative way to construct objects works
317+------------------------------------------------------------------------------
318+
319+objectConstruction :: [Assertion]
320+objectConstruction = [
321+ assertEqual "Equal objects constructed differently" recommended notRecommended
322+ ]
323+ where
324+ recommended = object ["foo" .= True, "bar" .= (-1 :: Int)]
325+ notRecommended = Object (mconcat ["foo" .= True, "bar" .= (-1 :: Int)])
326+
282-------------------------------------------------------------------------------
327-------------------------------------------------------------------------------
283-- ToJSONKey
328-- ToJSONKey
284-------------------------------------------------------------------------------
329-------------------------------------------------------------------------------
@@ -339,13 +384,13 @@ issue351 = [
339-- Comparison between bytestring and text encoders
384-- Comparison between bytestring and text encoders
340------------------------------------------------------------------------------
385------------------------------------------------------------------------------
341
386
342−ioTests :: IO [Test]
387+ioTests :: IO [TestTree]
343ioTests = do
388ioTests = do
344 enc <- encoderComparisonTests
389 enc <- encoderComparisonTests
345 js <- jsonTestSuite
390 js <- jsonTestSuite
346 return [enc, js]
391 return [enc, js]
347
392
348−encoderComparisonTests :: IO Test
393+encoderComparisonTests :: IO TestTree
349encoderComparisonTests = do
394encoderComparisonTests = do
350 encoderTests <- forM testFiles $ \file0 -> do
395 encoderTests <- forM testFiles $ \file0 -> do
351 let file = "benchmarks/json-data/" ++ file0
396 let file = "benchmarks/json-data/" ++ file0
@@ -405,7 +450,7 @@ unescapeString = do
405
450
406-- JSONTestSuite
451-- JSONTestSuite
407
452
408−jsonTestSuiteTest :: FilePath -> Test
453+jsonTestSuiteTest :: FilePath -> TestTree
409jsonTestSuiteTest path = testCase fileName $ do
454jsonTestSuiteTest path = testCase fileName $ do
410 payload <- L.readFile path
455 payload <- L.readFile path
411 let result = eitherDecode payload :: Either String Value
456 let result = eitherDecode payload :: Either String Value
@@ -420,7 +465,7 @@ jsonTestSuiteTest path = testCase fileName $ do
420-- Build a collection of tests based on the current contents of the
465-- Build a collection of tests based on the current contents of the
421-- JSONTestSuite test directories.
466-- JSONTestSuite test directories.
422
467
423−jsonTestSuite :: IO Test
468+jsonTestSuite :: IO TestTree
424jsonTestSuite = do
469jsonTestSuite = do
425 let suitePath = "tests/JSONTestSuite"
470 let suitePath = "tests/JSONTestSuite"
426 let suites = ["test_parsing", "test_transform"]
471 let suites = ["test_parsing", "test_transform"]
@@ -495,13 +540,14 @@ showOptions =
495 ++ ", sumEncoding = TaggedObject {tagFieldName = \"tag\", contentsFieldName = \"contents\"}"
540 ++ ", sumEncoding = TaggedObject {tagFieldName = \"tag\", contentsFieldName = \"contents\"}"
496 ++ ", unwrapUnaryRecords = False"
541 ++ ", unwrapUnaryRecords = False"
497 ++ ", tagSingleConstructors = False"
542 ++ ", tagSingleConstructors = False"
543+ ++ ", rejectUnknownFields = False"
498 ++ "}")
544 ++ "}")
499 (show defaultOptions)
545 (show defaultOptions)
500
546
501newtype SingleMaybeField = SingleMaybeField { smf :: Maybe Int }
547newtype SingleMaybeField = SingleMaybeField { smf :: Maybe Int }
502 deriving (Eq, Show, Generic)
548 deriving (Eq, Show, Generic)
503
549
504−singleMaybeField :: [Test]
550+singleMaybeField :: [TestTree]
505singleMaybeField = do
551singleMaybeField = do
506 (gName, gToJSON, gToEncoding, gFromJSON) <-
552 (gName, gToJSON, gToEncoding, gFromJSON) <-
507 [ ("generic", genericToJSON opts, genericToEncoding opts, parse (genericParseJSON opts))
553 [ ("generic", genericToJSON opts, genericToEncoding opts, parse (genericParseJSON opts))
@@ -515,9 +561,200 @@ singleMaybeField = do
515 v = SingleMaybeField Nothing
561 v = SingleMaybeField Nothing
516 opts = defaultOptions{omitNothingFields=True,unwrapUnaryRecords=True}
562 opts = defaultOptions{omitNothingFields=True,unwrapUnaryRecords=True}
517
563
564+
565+newtype EmbeddedJSONTest = EmbeddedJSONTest Int
566+ deriving (Eq, Show)
567+
568+instance FromJSON EmbeddedJSONTest where
569+ parseJSON =
570+ withObject "Object" $ \o ->
571+ EmbeddedJSONTest <$> (o .: "prop" >>= withEmbeddedJSON "Quoted Int" parseJSON)
572+
573+withEmbeddedJSONTest :: Assertion
574+withEmbeddedJSONTest =
575+ assertEqual "Unquote embedded JSON" (Right $ EmbeddedJSONTest 1) (eitherDecode "{\"prop\":\"1\"}")
576+
577+-- Regression test for https://github.com/bos/aeson/issues/627
578+newtype SingleFieldCon = SingleFieldCon Int deriving (Eq, Show, Generic)
579+
580+instance FromJSON SingleFieldCon where
581+ parseJSON = genericParseJSON defaultOptions{unwrapUnaryRecords=True}
582+ -- This option should have no effect on this type
583+
584+singleFieldCon :: Assertion
585+singleFieldCon =
586+ assertEqual "fromJSON" (Right (SingleFieldCon 0)) (eitherDecode "0")
587+
588+newtype UnknownFields = UnknownFields { knownField :: Int }
589+ deriving (Eq, Show, Generic)
590+newtype UnknownFieldsTag = UnknownFieldsTag { tag :: Int }
591+ deriving (Eq, Show, Generic)
592+newtype UnknownFieldsUnaryTagged = UnknownFieldsUnaryTagged { knownFieldUnaryTagged :: Int }
593+ deriving (Eq, Show, Generic)
594+data UnknownFieldsSum
595+ = UnknownFields1 { knownField1 :: Int }
596+ | UnknownFields2 { knownField2 :: Int }
597+ deriving (Eq, Show, Generic)
598+
599+unknownFields :: [TestTree]
600+unknownFields = concat
601+ [ testsUnary
602+ "unary-unknown"
603+ (object [("knownField", Number 1), ("unknownField", Number 1)])
604+ (Error "nknown fields: [\"unknownField\"]" :: Result UnknownFields)
605+ , testsUnary
606+ "unary-unknown-tag"
607+ (object [("knownField", Number 1), ("tag", String "UnknownFields")])
608+ (Error "nknown fields: [\"tag\"]" :: Result UnknownFields)
609+ , testsUnaryTag
610+ "unary-explicit-tag"
611+ (object [("tag", Number 1)])
612+ (Success $ UnknownFieldsTag 1)
613+ , testsSum
614+ "sum-tag"
615+ (object [("knownField1", Number 1), ("tag", String "UnknownFields1")])
616+ (Success $ UnknownFields1 1)
617+ , testsSum
618+ "sum-unknown-in-branch"
619+ (object [("knownField1", Number 1), ("knownField2", Number 1), ("tag", String "UnknownFields1")])
620+ (Error "nknown fields: [\"knownField2\"]" :: Result UnknownFieldsSum)
621+ , testsSum
622+ "sum-unknown"
623+ (object [("knownField1", Number 1), ("unknownField", Number 1), ("tag", String "UnknownFields1")])
624+ (Error "nknown fields: [\"unknownField\"]" :: Result UnknownFieldsSum)
625+ , testsTagged
626+ "unary-tagged"
627+ (object [("knownFieldUnaryTagged", Number 1), ("tag", String "UnknownFieldsUnaryTagged")])
628+ (Success $ UnknownFieldsUnaryTagged 1)
629+ , -- Just a case to verify that the tag isn't optional, this is likely already tested by other unit tests
630+ testsTagged
631+ "unary-tagged-notag"
632+ (object [("knownFieldUnaryTagged", Number 1)])
633+ (Error "key \"tag\" not found" :: Result UnknownFieldsUnaryTagged)
634+ , testsTagged
635+ "unary-tagged-unknown"
636+ (object [ ("knownFieldUnaryTagged", Number 1), ("unknownField", Number 1)
637+ , ("tag", String "UnknownFieldsUnaryTagged")])
638+ (Error "nknown fields: [\"unknownField\"]" :: Result UnknownFieldsUnaryTagged)
639+ ]
640+ where
641+ opts = defaultOptions{rejectUnknownFields=True}
642+ taggedOpts = opts{tagSingleConstructors=True}
643+ assertApprox :: (Show a, Eq a) => Result a -> Result a -> IO ()
644+ assertApprox (Error expected) (Error actual) | expected `isSuffixOf` actual = return ()
645+ assertApprox expected actual = assertEqual "fromJSON" expected actual
646+ testsBase :: (Show a, Eq a) => (Value -> Result a) -> (Value -> Result a)
647+ -> String -> Value -> Result a -> [TestTree]
648+ testsBase th g name value expected =
649+ [ testCase (name ++ "-th") $ assertApprox expected (th value)
650+ , testCase (name ++ "-generic") $ assertApprox expected (g value)
651+ ]
652+ testsUnary :: String -> Value -> Result UnknownFields -> [TestTree]
653+ testsUnary = testsBase fromJSON (parse (genericParseJSON opts))
654+ testsUnaryTag :: String -> Value -> Result UnknownFieldsTag -> [TestTree]
655+ testsUnaryTag = testsBase fromJSON (parse (genericParseJSON opts))
656+ testsSum :: String -> Value -> Result UnknownFieldsSum -> [TestTree]
657+ testsSum = testsBase fromJSON (parse (genericParseJSON opts))
658+ testsTagged :: String -> Value -> Result UnknownFieldsUnaryTagged -> [TestTree]
659+ testsTagged = testsBase fromJSON (parse (genericParseJSON taggedOpts))
660+
661+testParser :: (Eq a, Show a)
662+ => String -> Parser a -> S.ByteString -> Either String a -> TestTree
663+testParser name json_ s expected =
664+ testCase name (parseOnly json_ s @?= expected)
665+
666+keyOrdering :: [TestTree]
667+keyOrdering =
668+ [ testParser "json" json
669+ "{\"k\":true,\"k\":false}" $
670+ Right (Object (HashMap.fromList [("k", Bool True)]))
671+ , testParser "jsonLast" jsonLast
672+ "{\"k\":true,\"k\":false}" $
673+ Right (Object (HashMap.fromList [("k", Bool False)]))
674+ , testParser "jsonAccum" jsonAccum
675+ "{\"k\":true,\"k\":false}" $
676+ Right (Object (HashMap.fromList [("k", Array (Vector.fromList [Bool True, Bool False]))]))
677+ , testParser "jsonNoDup" jsonNoDup
678+ "{\"k\":true,\"k\":false}" $
679+ Left "Failed reading: found duplicate key: \"k\""
680+
681+ , testParser "json'" json'
682+ "{\"k\":true,\"k\":false}" $
683+ Right (Object (HashMap.fromList [("k", Bool True)]))
684+ , testParser "jsonLast'" jsonLast'
685+ "{\"k\":true,\"k\":false}" $
686+ Right (Object (HashMap.fromList [("k", Bool False)]))
687+ , testParser "jsonAccum'" jsonAccum'
688+ "{\"k\":true,\"k\":false}" $
689+ Right (Object (HashMap.fromList [("k", Array (Vector.fromList [Bool True, Bool False]))]))
690+ , testParser "jsonNoDup'" jsonNoDup'
691+ "{\"k\":true,\"k\":false}" $
692+ Left "Failed reading: found duplicate key: \"k\""
693+ ]
694+
695+ratioDenominator0 :: Assertion
696+ratioDenominator0 =
697+ assertEqual "Ratio with denominator 0"
698+ (Left "Error in $: Ratio denominator was 0")
699+ (eitherDecode "{ \"numerator\": 1, \"denominator\": 0 }" :: Either String Rational)
700+
701+rationalNumber :: Assertion
702+rationalNumber =
703+ assertEqual "Ratio with denominator 0"
704+ (Right 1.37)
705+ (eitherDecode "1.37" :: Either String Rational)
706+
707+bigRationalDecoding :: Assertion
708+bigRationalDecoding =
709+ assertEqual "Decoding an Integer with a large exponent should fail"
710+ (Left "Error in $: parsing Ratio failed, found a number with exponent 2000, but it must not be greater than 1024 or less than -1024")
711+ ((eitherDecode :: L.ByteString -> Either String Rational) "1e2000")
712+
713+smallRationalDecoding :: Assertion
714+smallRationalDecoding =
715+ assertEqual "Decoding an Integer with a large exponent should fail"
716+ (Left "Error in $: parsing Ratio failed, found a number with exponent -2000, but it must not be greater than 1024 or less than -1024")
717+ ((eitherDecode :: L.ByteString -> Either String Rational) "1e-2000")
718+
719+
720+bigScientificExponent :: Assertion
721+bigScientificExponent =
722+ assertEqual "Encoding an integral scientific with a large exponent should normalize it"
723+ "1.0e2000"
724+ (encode (scientific 1 2000 :: Scientific))
725+
726+bigIntegerDecoding :: Assertion
727+bigIntegerDecoding =
728+ assertEqual "Decoding an Integer with a large exponent should fail"
729+ (Left "Error in $: parsing Integer failed, found a number with exponent 2000, but it must not be greater than 1024")
730+ ((eitherDecode :: L.ByteString -> Either String Integer) "1e2000")
731+
732+bigNaturalDecoding :: Assertion
733+bigNaturalDecoding =
734+ assertEqual "Decoding a Natural with a large exponent should fail"
735+ (Left "Error in $: parsing Natural failed, found a number with exponent 2000, but it must not be greater than 1024")
736+ ((eitherDecode :: L.ByteString -> Either String Natural) "1e2000")
737+
738+bigIntegerKeyDecoding :: Assertion
739+bigIntegerKeyDecoding =
740+ assertEqual "Decoding an Integer key with a large exponent should fail"
741+ (Left "Error in $['1e2000']: parsing Integer failed, found a number with exponent 2000, but it must not be greater than 1024")
742+ ((eitherDecode :: L.ByteString -> Either String (HashMap Integer Value)) "{ \"1e2000\": null }")
743+
744+bigNaturalKeyDecoding :: Assertion
745+bigNaturalKeyDecoding =
746+ assertEqual "Decoding an Integer key with a large exponent should fail"
747+ (Left "Error in $['1e2000']: found a number with exponent 2000, but it must not be greater than 1024")
748+ ((eitherDecode :: L.ByteString -> Either String (HashMap Natural Value)) "{ \"1e2000\": null }")
749+
518deriveJSON defaultOptions{omitNothingFields=True} ''MyRecord
750deriveJSON defaultOptions{omitNothingFields=True} ''MyRecord
519
751
520deriveToJSON defaultOptions ''Foo
752deriveToJSON defaultOptions ''Foo
521deriveToJSON1 defaultOptions ''Foo
753deriveToJSON1 defaultOptions ''Foo
522
754
523deriveJSON defaultOptions{omitNothingFields=True,unwrapUnaryRecords=True} ''SingleMaybeField
755deriveJSON defaultOptions{omitNothingFields=True,unwrapUnaryRecords=True} ''SingleMaybeField
756+
757+deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFields
758+deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFieldsTag
759+deriveJSON defaultOptions{tagSingleConstructors=True,rejectUnknownFields=True} ''UnknownFieldsUnaryTagged
760+deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFieldsSum
tests/UnitTests/NullaryConstructors.hs+8−3
@@ -1,4 +1,5 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1−{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE OverloadedStrings #-}
2
3
3{-# OPTIONS_GHC -fno-warn-deprecations #-}
4{-# OPTIONS_GHC -fno-warn-deprecations #-}
4
5
@@ -7,16 +8,16 @@ module UnitTests.NullaryConstructors
7 nullaryConstructors
8 nullaryConstructors
8 ) where
9 ) where
9
10
10−import Prelude ()
11import Prelude.Compat
11import Prelude.Compat
12
12
13import Data.Aeson (decode, eitherDecode, fromEncoding, Value)
13import Data.Aeson (decode, eitherDecode, fromEncoding, Value)
14import Data.Aeson.Internal (IResult (..), iparse)
14import Data.Aeson.Internal (IResult (..), iparse)
15import Data.Aeson.Types (Parser)
15import Data.Aeson.Types (Parser)
16import Data.ByteString.Builder (toLazyByteString)
16import Data.ByteString.Builder (toLazyByteString)
17+import Data.Foldable (for_)
17import Data.Maybe (fromJust)
18import Data.Maybe (fromJust)
18import Encoders
19import Encoders
19−import Test.HUnit ((@=?), Assertion)
20+import Test.Tasty.HUnit ((@=?), Assertion)
20import Types
21import Types
21import qualified Data.ByteString.Lazy.Char8 as L
22import qualified Data.ByteString.Lazy.Char8 as L
22
23
@@ -52,6 +53,10 @@ nullaryConstructors =
52 -- Make sure that the old `"contents" : []' is still allowed
53 -- Make sure that the old `"contents" : []' is still allowed
53 , ISuccess C1 @=? parse thNullaryParseJSONTaggedObject (dec "{\"tag\":\"c1\",\"contents\":[]}")
54 , ISuccess C1 @=? parse thNullaryParseJSONTaggedObject (dec "{\"tag\":\"c1\",\"contents\":[]}")
54 , ISuccess C1 @=? parse gNullaryParseJSONTaggedObject (dec "{\"tag\":\"c1\",\"contents\":[]}")
55 , ISuccess C1 @=? parse gNullaryParseJSONTaggedObject (dec "{\"tag\":\"c1\",\"contents\":[]}")
56+
57+ , for_ [("kC1", C1), ("kC2", C2), ("kC3", C3)] $ \(jkey, key) -> do
58+ Right jkey @=? gNullaryToJSONKey key
59+ ISuccess key @=? parse gNullaryFromJSONKey jkey
55 ]
60 ]
56 where
61 where
57 enc = eitherDecode . toLazyByteString . fromEncoding
62 enc = eitherDecode . toLazyByteString . fromEncoding
tests/golden/generic.expectednew file+48
@@ -0,0 +1,48 @@
1+OneConstructor
2+Error in $: parsing Types.OneConstructor(OneConstructor) failed, expected Array, but encountered String
3+Error in $: parsing Types.OneConstructor(OneConstructor) failed, expected an empty Array, but encountered an Array of length 1
4+Nullary
5+Error in $: parsing Types.Nullary failed, expected one of the tags ["C1","C2","C3"], but found tag "X"
6+Error in $: parsing Types.Nullary failed, expected String, but encountered Array
7+SomeType (tagged)
8+Error in $.contents: parsing Int failed, expected Number, but encountered Boolean
9+Error in $: parsing Types.SomeType(Unary) failed, key "contents" not found
10+Error in $: parsing Types.SomeType(Record) failed, key "testone" not found
11+Error in $.testone: parsing Double failed, unexpected Boolean
12+Error in $.tag: parsing Types.SomeType failed, expected tag field to be one of ["nullary","unary","product","record","list"], but found tag "X"
13+Error in $: parsing Types.SomeType failed, expected Object with key "tag" containing one of ["nullary","unary","product","record","list"], key "tag" not found
14+Error in $: parsing Types.SomeType failed, expected Object, but encountered Array
15+SomeType (single-field)
16+Error in $.unary: parsing Int failed, expected Number, but encountered Object
17+Error in $.unary: parsing Int failed, expected Number, but encountered Array
18+Error in $: parsing Types.SomeType failed, expected an Object with a single pair where the tag is one of ["nullary","unary","product","record","list"], but found tag "X"
19+Error in $: parsing Types.SomeType failed, expected an Object with a single pair, but found 2 pairs
20+Error in $: parsing Types.SomeType failed, expected an Object with a single pair, but found 0 pairs
21+Error in $: parsing Types.SomeType failed, expected Object, but encountered Array
22+Error in $: not enough input. Expecting ':'
23+Error in $: not enough input. Expecting object value
24+Error in $: not enough input. Expecting ',' or '}'
25+SomeType (two-element array)
26+Error in $[1]: parsing Int failed, expected Number, but encountered Boolean
27+Error in $[1]: parsing Types.SomeType(Record) failed, expected Object, but encountered Null
28+Error in $[0]: parsing Types.SomeType failed, expected tag of the 2-element Array to be one of ["nullary","unary","product","record","list"], but found tag "X"
29+Error in $[0]: parsing Types.SomeType failed, tag element is not a String
30+Error in $: parsing Types.SomeType failed, expected a 2-element Array, but encountered an Array of length 0
31+Error in $: parsing Types.SomeType failed, expected Array, but encountered Object
32+Error in $: not enough input. Expecting ',' or ']'
33+Error in $: not enough input. Expecting json list value
34+SomeType (reject unknown fields)
35+Error in $: parsing Types.SomeType(Record) failed, unknown fields: ["testZero"]
36+Error in $: parsing Types.SomeType failed, expected Object with key "tag" containing one of ["nullary","unary","product","record","list"], key "tag" not found
37+Error in $: parsing Types.SomeType(Record) failed, unknown fields: ["testtwo","testone","testthree"]
38+Foo (reject unknown fields)
39+Error in $: parsing Types.Foo(Foo) failed, unknown fields: ["tag"]
40+Foo (reject unknown fields, tagged single)
41+Error in $: parsing Types.Foo(Foo) failed, unknown fields: ["unknownField"]
42+EitherTextInt
43+Error in $: parsing Types.EitherTextInt(NoneNullary) failed, expected tag "nonenullary", but found tag "X"
44+Error in $: parsing Types.EitherTextInt(NoneNullary) failed, expected String, but encountered Array
45+Product2 Int Bool
46+Error in $[1]: expected Bool, but encountered Null
47+Error in $: parsing Types.Product2(Product2) failed, expected an Array of length 2, but encountered an Array of length 0
48+Error in $: parsing Types.Product2(Product2) failed, expected Array, but encountered Object
tests/golden/simple.expectednew file+22
@@ -0,0 +1,22 @@
1+Int
2+Error in $: parsing Int failed, expected Number, but encountered String
3+Error in $: parsing Int failed, expected Number, but encountered Array
4+Error in $: parsing Int failed, expected Number, but encountered Object
5+Error in $: parsing Int failed, expected Number, but encountered Null
6+Integer
7+Error in $: parsing Integer failed, unexpected floating number 44.44
8+Natural
9+Error in $: parsing Natural failed, unexpected floating number 44.44
10+Error in $: parsing Natural failed, unexpected negative number -50
11+String
12+Error in $: expected String, but encountered Number
13+Error in $: expected String, but encountered Array
14+Error in $: expected String, but encountered Object
15+Error in $: expected String, but encountered Null
16+HashMap
17+Error in $: parsing HashMap failed, expected Object, but encountered String
18+Error in $: parsing HashMap failed, expected Object, but encountered Array
19+Either
20+Error in $[1].Left[1]: expected Bool, but encountered Number
21+Seq
22+Error in $[2]: parsing Int failed, expected Number, but encountered Boolean
travis/cabal_macros.hnew file+448
@@ -0,0 +1,448 @@
1+/* DO NOT EDIT: This file is automatically generated by Cabal */
2+
3+/* package aeson-1.4.4.0 */
4+#ifndef VERSION_aeson
5+#define VERSION_aeson "1.4.4.0"
6+#endif /* VERSION_aeson */
7+#ifndef MIN_VERSION_aeson
8+#define MIN_VERSION_aeson(major1,major2,minor) (\
9+ (major1) < 1 || \
10+ (major1) == 1 && (major2) < 4 || \
11+ (major1) == 1 && (major2) == 4 && (minor) <= 4)
12+#endif /* MIN_VERSION_aeson */
13+
14+/* package Diff-0.3.4 */
15+#ifndef VERSION_Diff
16+#define VERSION_Diff "0.3.4"
17+#endif /* VERSION_Diff */
18+#ifndef MIN_VERSION_Diff
19+#define MIN_VERSION_Diff(major1,major2,minor) (\
20+ (major1) < 0 || \
21+ (major1) == 0 && (major2) < 3 || \
22+ (major1) == 0 && (major2) == 3 && (minor) <= 4)
23+#endif /* MIN_VERSION_Diff */
24+
25+/* package QuickCheck-2.13.1 */
26+#ifndef VERSION_QuickCheck
27+#define VERSION_QuickCheck "2.13.1"
28+#endif /* VERSION_QuickCheck */
29+#ifndef MIN_VERSION_QuickCheck
30+#define MIN_VERSION_QuickCheck(major1,major2,minor) (\
31+ (major1) < 2 || \
32+ (major1) == 2 && (major2) < 13 || \
33+ (major1) == 2 && (major2) == 13 && (minor) <= 1)
34+#endif /* MIN_VERSION_QuickCheck */
35+
36+/* package aeson-1.4.4.0 */
37+#ifndef VERSION_aeson
38+#define VERSION_aeson "1.4.4.0"
39+#endif /* VERSION_aeson */
40+#ifndef MIN_VERSION_aeson
41+#define MIN_VERSION_aeson(major1,major2,minor) (\
42+ (major1) < 1 || \
43+ (major1) == 1 && (major2) < 4 || \
44+ (major1) == 1 && (major2) == 4 && (minor) <= 4)
45+#endif /* MIN_VERSION_aeson */
46+
47+/* package attoparsec-0.13.2.2 */
48+#ifndef VERSION_attoparsec
49+#define VERSION_attoparsec "0.13.2.2"
50+#endif /* VERSION_attoparsec */
51+#ifndef MIN_VERSION_attoparsec
52+#define MIN_VERSION_attoparsec(major1,major2,minor) (\
53+ (major1) < 0 || \
54+ (major1) == 0 && (major2) < 13 || \
55+ (major1) == 0 && (major2) == 13 && (minor) <= 2)
56+#endif /* MIN_VERSION_attoparsec */
57+
58+/* package base-4.12.0.0 */
59+#ifndef VERSION_base
60+#define VERSION_base "4.12.0.0"
61+#endif /* VERSION_base */
62+#ifndef MIN_VERSION_base
63+#define MIN_VERSION_base(major1,major2,minor) (\
64+ (major1) < 4 || \
65+ (major1) == 4 && (major2) < 12 || \
66+ (major1) == 4 && (major2) == 12 && (minor) <= 0)
67+#endif /* MIN_VERSION_base */
68+
69+/* package base-compat-0.10.5 */
70+#ifndef VERSION_base_compat
71+#define VERSION_base_compat "0.10.5"
72+#endif /* VERSION_base_compat */
73+#ifndef MIN_VERSION_base_compat
74+#define MIN_VERSION_base_compat(major1,major2,minor) (\
75+ (major1) < 0 || \
76+ (major1) == 0 && (major2) < 10 || \
77+ (major1) == 0 && (major2) == 10 && (minor) <= 5)
78+#endif /* MIN_VERSION_base_compat */
79+
80+/* package base-orphans-0.8.1 */
81+#ifndef VERSION_base_orphans
82+#define VERSION_base_orphans "0.8.1"
83+#endif /* VERSION_base_orphans */
84+#ifndef MIN_VERSION_base_orphans
85+#define MIN_VERSION_base_orphans(major1,major2,minor) (\
86+ (major1) < 0 || \
87+ (major1) == 0 && (major2) < 8 || \
88+ (major1) == 0 && (major2) == 8 && (minor) <= 1)
89+#endif /* MIN_VERSION_base_orphans */
90+
91+/* package base16-bytestring-0.1.1.6 */
92+#ifndef VERSION_base16_bytestring
93+#define VERSION_base16_bytestring "0.1.1.6"
94+#endif /* VERSION_base16_bytestring */
95+#ifndef MIN_VERSION_base16_bytestring
96+#define MIN_VERSION_base16_bytestring(major1,major2,minor) (\
97+ (major1) < 0 || \
98+ (major1) == 0 && (major2) < 1 || \
99+ (major1) == 0 && (major2) == 1 && (minor) <= 1)
100+#endif /* MIN_VERSION_base16_bytestring */
101+
102+/* package bytestring-0.10.8.2 */
103+#ifndef VERSION_bytestring
104+#define VERSION_bytestring "0.10.8.2"
105+#endif /* VERSION_bytestring */
106+#ifndef MIN_VERSION_bytestring
107+#define MIN_VERSION_bytestring(major1,major2,minor) (\
108+ (major1) < 0 || \
109+ (major1) == 0 && (major2) < 10 || \
110+ (major1) == 0 && (major2) == 10 && (minor) <= 8)
111+#endif /* MIN_VERSION_bytestring */
112+
113+/* package containers-0.6.0.1 */
114+#ifndef VERSION_containers
115+#define VERSION_containers "0.6.0.1"
116+#endif /* VERSION_containers */
117+#ifndef MIN_VERSION_containers
118+#define MIN_VERSION_containers(major1,major2,minor) (\
119+ (major1) < 0 || \
120+ (major1) == 0 && (major2) < 6 || \
121+ (major1) == 0 && (major2) == 6 && (minor) <= 0)
122+#endif /* MIN_VERSION_containers */
123+
124+/* package directory-1.3.3.0 */
125+#ifndef VERSION_directory
126+#define VERSION_directory "1.3.3.0"
127+#endif /* VERSION_directory */
128+#ifndef MIN_VERSION_directory
129+#define MIN_VERSION_directory(major1,major2,minor) (\
130+ (major1) < 1 || \
131+ (major1) == 1 && (major2) < 3 || \
132+ (major1) == 1 && (major2) == 3 && (minor) <= 3)
133+#endif /* MIN_VERSION_directory */
134+
135+/* package dlist-0.8.0.6 */
136+#ifndef VERSION_dlist
137+#define VERSION_dlist "0.8.0.6"
138+#endif /* VERSION_dlist */
139+#ifndef MIN_VERSION_dlist
140+#define MIN_VERSION_dlist(major1,major2,minor) (\
141+ (major1) < 0 || \
142+ (major1) == 0 && (major2) < 8 || \
143+ (major1) == 0 && (major2) == 8 && (minor) <= 0)
144+#endif /* MIN_VERSION_dlist */
145+
146+/* package filepath-1.4.2.1 */
147+#ifndef VERSION_filepath
148+#define VERSION_filepath "1.4.2.1"
149+#endif /* VERSION_filepath */
150+#ifndef MIN_VERSION_filepath
151+#define MIN_VERSION_filepath(major1,major2,minor) (\
152+ (major1) < 1 || \
153+ (major1) == 1 && (major2) < 4 || \
154+ (major1) == 1 && (major2) == 4 && (minor) <= 2)
155+#endif /* MIN_VERSION_filepath */
156+
157+/* package generic-deriving-1.12.4 */
158+#ifndef VERSION_generic_deriving
159+#define VERSION_generic_deriving "1.12.4"
160+#endif /* VERSION_generic_deriving */
161+#ifndef MIN_VERSION_generic_deriving
162+#define MIN_VERSION_generic_deriving(major1,major2,minor) (\
163+ (major1) < 1 || \
164+ (major1) == 1 && (major2) < 12 || \
165+ (major1) == 1 && (major2) == 12 && (minor) <= 4)
166+#endif /* MIN_VERSION_generic_deriving */
167+
168+/* package ghc-prim-0.5.3 */
169+#ifndef VERSION_ghc_prim
170+#define VERSION_ghc_prim "0.5.3"
171+#endif /* VERSION_ghc_prim */
172+#ifndef MIN_VERSION_ghc_prim
173+#define MIN_VERSION_ghc_prim(major1,major2,minor) (\
174+ (major1) < 0 || \
175+ (major1) == 0 && (major2) < 5 || \
176+ (major1) == 0 && (major2) == 5 && (minor) <= 3)
177+#endif /* MIN_VERSION_ghc_prim */
178+
179+/* package hashable-1.3.0.0 */
180+#ifndef VERSION_hashable
181+#define VERSION_hashable "1.3.0.0"
182+#endif /* VERSION_hashable */
183+#ifndef MIN_VERSION_hashable
184+#define MIN_VERSION_hashable(major1,major2,minor) (\
185+ (major1) < 1 || \
186+ (major1) == 1 && (major2) < 3 || \
187+ (major1) == 1 && (major2) == 3 && (minor) <= 0)
188+#endif /* MIN_VERSION_hashable */
189+
190+/* package hashable-time-0.2.0.2 */
191+#ifndef VERSION_hashable_time
192+#define VERSION_hashable_time "0.2.0.2"
193+#endif /* VERSION_hashable_time */
194+#ifndef MIN_VERSION_hashable_time
195+#define MIN_VERSION_hashable_time(major1,major2,minor) (\
196+ (major1) < 0 || \
197+ (major1) == 0 && (major2) < 2 || \
198+ (major1) == 0 && (major2) == 2 && (minor) <= 0)
199+#endif /* MIN_VERSION_hashable_time */
200+
201+/* package integer-logarithms-1.0.3 */
202+#ifndef VERSION_integer_logarithms
203+#define VERSION_integer_logarithms "1.0.3"
204+#endif /* VERSION_integer_logarithms */
205+#ifndef MIN_VERSION_integer_logarithms
206+#define MIN_VERSION_integer_logarithms(major1,major2,minor) (\
207+ (major1) < 1 || \
208+ (major1) == 1 && (major2) < 0 || \
209+ (major1) == 1 && (major2) == 0 && (minor) <= 3)
210+#endif /* MIN_VERSION_integer_logarithms */
211+
212+/* package quickcheck-instances-0.3.21 */
213+#ifndef VERSION_quickcheck_instances
214+#define VERSION_quickcheck_instances "0.3.21"
215+#endif /* VERSION_quickcheck_instances */
216+#ifndef MIN_VERSION_quickcheck_instances
217+#define MIN_VERSION_quickcheck_instances(major1,major2,minor) (\
218+ (major1) < 0 || \
219+ (major1) == 0 && (major2) < 3 || \
220+ (major1) == 0 && (major2) == 3 && (minor) <= 21)
221+#endif /* MIN_VERSION_quickcheck_instances */
222+
223+/* package scientific-0.3.6.2 */
224+#ifndef VERSION_scientific
225+#define VERSION_scientific "0.3.6.2"
226+#endif /* VERSION_scientific */
227+#ifndef MIN_VERSION_scientific
228+#define MIN_VERSION_scientific(major1,major2,minor) (\
229+ (major1) < 0 || \
230+ (major1) == 0 && (major2) < 3 || \
231+ (major1) == 0 && (major2) == 3 && (minor) <= 6)
232+#endif /* MIN_VERSION_scientific */
233+
234+/* package tagged-0.8.6 */
235+#ifndef VERSION_tagged
236+#define VERSION_tagged "0.8.6"
237+#endif /* VERSION_tagged */
238+#ifndef MIN_VERSION_tagged
239+#define MIN_VERSION_tagged(major1,major2,minor) (\
240+ (major1) < 0 || \
241+ (major1) == 0 && (major2) < 8 || \
242+ (major1) == 0 && (major2) == 8 && (minor) <= 6)
243+#endif /* MIN_VERSION_tagged */
244+
245+/* package tasty-1.2.3 */
246+#ifndef VERSION_tasty
247+#define VERSION_tasty "1.2.3"
248+#endif /* VERSION_tasty */
249+#ifndef MIN_VERSION_tasty
250+#define MIN_VERSION_tasty(major1,major2,minor) (\
251+ (major1) < 1 || \
252+ (major1) == 1 && (major2) < 2 || \
253+ (major1) == 1 && (major2) == 2 && (minor) <= 3)
254+#endif /* MIN_VERSION_tasty */
255+
256+/* package tasty-golden-2.3.2 */
257+#ifndef VERSION_tasty_golden
258+#define VERSION_tasty_golden "2.3.2"
259+#endif /* VERSION_tasty_golden */
260+#ifndef MIN_VERSION_tasty_golden
261+#define MIN_VERSION_tasty_golden(major1,major2,minor) (\
262+ (major1) < 2 || \
263+ (major1) == 2 && (major2) < 3 || \
264+ (major1) == 2 && (major2) == 3 && (minor) <= 2)
265+#endif /* MIN_VERSION_tasty_golden */
266+
267+/* package tasty-hunit-0.10.0.2 */
268+#ifndef VERSION_tasty_hunit
269+#define VERSION_tasty_hunit "0.10.0.2"
270+#endif /* VERSION_tasty_hunit */
271+#ifndef MIN_VERSION_tasty_hunit
272+#define MIN_VERSION_tasty_hunit(major1,major2,minor) (\
273+ (major1) < 0 || \
274+ (major1) == 0 && (major2) < 10 || \
275+ (major1) == 0 && (major2) == 10 && (minor) <= 0)
276+#endif /* MIN_VERSION_tasty_hunit */
277+
278+/* package tasty-quickcheck-0.10.1 */
279+#ifndef VERSION_tasty_quickcheck
280+#define VERSION_tasty_quickcheck "0.10.1"
281+#endif /* VERSION_tasty_quickcheck */
282+#ifndef MIN_VERSION_tasty_quickcheck
283+#define MIN_VERSION_tasty_quickcheck(major1,major2,minor) (\
284+ (major1) < 0 || \
285+ (major1) == 0 && (major2) < 10 || \
286+ (major1) == 0 && (major2) == 10 && (minor) <= 1)
287+#endif /* MIN_VERSION_tasty_quickcheck */
288+
289+/* package template-haskell-2.14.0.0 */
290+#ifndef VERSION_template_haskell
291+#define VERSION_template_haskell "2.14.0.0"
292+#endif /* VERSION_template_haskell */
293+#ifndef MIN_VERSION_template_haskell
294+#define MIN_VERSION_template_haskell(major1,major2,minor) (\
295+ (major1) < 2 || \
296+ (major1) == 2 && (major2) < 14 || \
297+ (major1) == 2 && (major2) == 14 && (minor) <= 0)
298+#endif /* MIN_VERSION_template_haskell */
299+
300+/* package text-1.2.3.1 */
301+#ifndef VERSION_text
302+#define VERSION_text "1.2.3.1"
303+#endif /* VERSION_text */
304+#ifndef MIN_VERSION_text
305+#define MIN_VERSION_text(major1,major2,minor) (\
306+ (major1) < 1 || \
307+ (major1) == 1 && (major2) < 2 || \
308+ (major1) == 1 && (major2) == 2 && (minor) <= 3)
309+#endif /* MIN_VERSION_text */
310+
311+/* package time-1.8.0.2 */
312+#ifndef VERSION_time
313+#define VERSION_time "1.8.0.2"
314+#endif /* VERSION_time */
315+#ifndef MIN_VERSION_time
316+#define MIN_VERSION_time(major1,major2,minor) (\
317+ (major1) < 1 || \
318+ (major1) == 1 && (major2) < 8 || \
319+ (major1) == 1 && (major2) == 8 && (minor) <= 0)
320+#endif /* MIN_VERSION_time */
321+
322+/* package time-compat-1.9.2.2 */
323+#ifndef VERSION_time_compat
324+#define VERSION_time_compat "1.9.2.2"
325+#endif /* VERSION_time_compat */
326+#ifndef MIN_VERSION_time_compat
327+#define MIN_VERSION_time_compat(major1,major2,minor) (\
328+ (major1) < 1 || \
329+ (major1) == 1 && (major2) < 9 || \
330+ (major1) == 1 && (major2) == 9 && (minor) <= 2)
331+#endif /* MIN_VERSION_time_compat */
332+
333+/* package unordered-containers-0.2.10.0 */
334+#ifndef VERSION_unordered_containers
335+#define VERSION_unordered_containers "0.2.10.0"
336+#endif /* VERSION_unordered_containers */
337+#ifndef MIN_VERSION_unordered_containers
338+#define MIN_VERSION_unordered_containers(major1,major2,minor) (\
339+ (major1) < 0 || \
340+ (major1) == 0 && (major2) < 2 || \
341+ (major1) == 0 && (major2) == 2 && (minor) <= 10)
342+#endif /* MIN_VERSION_unordered_containers */
343+
344+/* package uuid-types-1.0.3 */
345+#ifndef VERSION_uuid_types
346+#define VERSION_uuid_types "1.0.3"
347+#endif /* VERSION_uuid_types */
348+#ifndef MIN_VERSION_uuid_types
349+#define MIN_VERSION_uuid_types(major1,major2,minor) (\
350+ (major1) < 1 || \
351+ (major1) == 1 && (major2) < 0 || \
352+ (major1) == 1 && (major2) == 0 && (minor) <= 3)
353+#endif /* MIN_VERSION_uuid_types */
354+
355+/* package vector-0.12.0.3 */
356+#ifndef VERSION_vector
357+#define VERSION_vector "0.12.0.3"
358+#endif /* VERSION_vector */
359+#ifndef MIN_VERSION_vector
360+#define MIN_VERSION_vector(major1,major2,minor) (\
361+ (major1) < 0 || \
362+ (major1) == 0 && (major2) < 12 || \
363+ (major1) == 0 && (major2) == 12 && (minor) <= 0)
364+#endif /* MIN_VERSION_vector */
365+
366+/* tool gcc-4.2.1 */
367+#ifndef TOOL_VERSION_gcc
368+#define TOOL_VERSION_gcc "4.2.1"
369+#endif /* TOOL_VERSION_gcc */
370+#ifndef MIN_TOOL_VERSION_gcc
371+#define MIN_TOOL_VERSION_gcc(major1,major2,minor) (\
372+ (major1) < 4 || \
373+ (major1) == 4 && (major2) < 2 || \
374+ (major1) == 4 && (major2) == 2 && (minor) <= 1)
375+#endif /* MIN_TOOL_VERSION_gcc */
376+
377+/* tool ghc-8.6.5 */
378+#ifndef TOOL_VERSION_ghc
379+#define TOOL_VERSION_ghc "8.6.5"
380+#endif /* TOOL_VERSION_ghc */
381+#ifndef MIN_TOOL_VERSION_ghc
382+#define MIN_TOOL_VERSION_ghc(major1,major2,minor) (\
383+ (major1) < 8 || \
384+ (major1) == 8 && (major2) < 6 || \
385+ (major1) == 8 && (major2) == 6 && (minor) <= 5)
386+#endif /* MIN_TOOL_VERSION_ghc */
387+
388+/* tool ghc-pkg-8.6.5 */
389+#ifndef TOOL_VERSION_ghc_pkg
390+#define TOOL_VERSION_ghc_pkg "8.6.5"
391+#endif /* TOOL_VERSION_ghc_pkg */
392+#ifndef MIN_TOOL_VERSION_ghc_pkg
393+#define MIN_TOOL_VERSION_ghc_pkg(major1,major2,minor) (\
394+ (major1) < 8 || \
395+ (major1) == 8 && (major2) < 6 || \
396+ (major1) == 8 && (major2) == 6 && (minor) <= 5)
397+#endif /* MIN_TOOL_VERSION_ghc_pkg */
398+
399+/* tool haddock-2.22.0 */
400+#ifndef TOOL_VERSION_haddock
401+#define TOOL_VERSION_haddock "2.22.0"
402+#endif /* TOOL_VERSION_haddock */
403+#ifndef MIN_TOOL_VERSION_haddock
404+#define MIN_TOOL_VERSION_haddock(major1,major2,minor) (\
405+ (major1) < 2 || \
406+ (major1) == 2 && (major2) < 22 || \
407+ (major1) == 2 && (major2) == 22 && (minor) <= 0)
408+#endif /* MIN_TOOL_VERSION_haddock */
409+
410+/* tool hpc-0.67 */
411+#ifndef TOOL_VERSION_hpc
412+#define TOOL_VERSION_hpc "0.67"
413+#endif /* TOOL_VERSION_hpc */
414+#ifndef MIN_TOOL_VERSION_hpc
415+#define MIN_TOOL_VERSION_hpc(major1,major2,minor) (\
416+ (major1) < 0 || \
417+ (major1) == 0 && (major2) < 67 || \
418+ (major1) == 0 && (major2) == 67 && (minor) <= 0)
419+#endif /* MIN_TOOL_VERSION_hpc */
420+
421+/* tool hsc2hs-0.68.5 */
422+#ifndef TOOL_VERSION_hsc2hs
423+#define TOOL_VERSION_hsc2hs "0.68.5"
424+#endif /* TOOL_VERSION_hsc2hs */
425+#ifndef MIN_TOOL_VERSION_hsc2hs
426+#define MIN_TOOL_VERSION_hsc2hs(major1,major2,minor) (\
427+ (major1) < 0 || \
428+ (major1) == 0 && (major2) < 68 || \
429+ (major1) == 0 && (major2) == 68 && (minor) <= 5)
430+#endif /* MIN_TOOL_VERSION_hsc2hs */
431+
432+/* tool runghc-8.6.5 */
433+#ifndef TOOL_VERSION_runghc
434+#define TOOL_VERSION_runghc "8.6.5"
435+#endif /* TOOL_VERSION_runghc */
436+#ifndef MIN_TOOL_VERSION_runghc
437+#define MIN_TOOL_VERSION_runghc(major1,major2,minor) (\
438+ (major1) < 8 || \
439+ (major1) == 8 && (major2) < 6 || \
440+ (major1) == 8 && (major2) == 6 && (minor) <= 5)
441+#endif /* MIN_TOOL_VERSION_runghc */
442+
443+#ifndef CURRENT_COMPONENT_ID
444+#define CURRENT_COMPONENT_ID "aeson-1.4.4.0-inplace-tests"
445+#endif /* CURRENT_COMPONENT_ID */
446+#ifndef CURRENT_PACKAGE_VERSION
447+#define CURRENT_PACKAGE_VERSION "1.4.4.0"
448+#endif /* CURRENT_PACKAGE_VERSION */
travis/install.sh+11
@@ -7,6 +7,11 @@ echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo
7stack --version
7stack --version
8
8
9case $BUILD in
9case $BUILD in
10+ hlint)
11+ echo "Downloading hlint"
12+ curl -sSL https://raw.github.com/ndmitchell/hlint/master/misc/run.sh > hlint.sh
13+ chmod +x hlint.sh
14+ ;;
10 stack)
15 stack)
11 stack --no-terminal --skip-ghc-check setup
16 stack --no-terminal --skip-ghc-check setup
12 stack --no-terminal --skip-ghc-check test --only-snapshot
17 stack --no-terminal --skip-ghc-check test --only-snapshot
@@ -44,4 +49,10 @@ case $BUILD in
44 cp -a $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/;
49 cp -a $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/;
45 fi
50 fi
46 ;;
51 ;;
52+ cabal2)
53+ cabal v2-update -v
54+ sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config
55+
56+ cabal v2-build --only-dependencies --enable-tests --enable-benchmarks --dry
57+ ;;
47esac
58esac
travis/script.sh+4−2
@@ -19,9 +19,11 @@ case $BUILD in
19 SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz &&
19 SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz &&
20 (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
20 (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
21 ;;
21 ;;
22+ cabal2)
23+ cabal v2-build --enable-tests --enable-benchmarks
24+ cabal v2-test --enable-tests --enable-benchmarks
25+ ;;
22 hlint)
26 hlint)
23− stack build --fast aeson --stack-yaml stack-lts8.yaml --system-ghc --no-terminal
24− stack install hlint-2.0.5 --stack-yaml stack-lts8.yaml --system-ghc --no-terminal
25 make lint
27 make lint
26 ;;
28 ;;
27esac
29esac