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+33−19
@@ -12,44 +12,58 @@ 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:
22− - env: BUILD=hlint CABALVER=2.0 GHCVER=8.2.2
23− compiler: ": #GHC 8.2.2"
24− addons: {apt: {packages: [cabal-install-2.0,ghc-8.2.2], sources: [hvr-ghc]}}
25− - env: BUILD=stack CABALVER=2.0 GHCVER=8.2.2 STACK_YAML=stack-bench.yaml
26− compiler: ": #GHC 8.2.2"
27− addons: {apt: {packages: [cabal-install-2.0,ghc-8.2.2], sources: [hvr-ghc]}}
28− - env: BUILD=stack CABALVER=2.0 GHCVER=8.2.2 STACK_YAML=stack-ffi-unescape.yaml
29− compiler: ": #GHC 8.2.2"
30− addons: {apt: {packages: [cabal-install-2.0,ghc-8.2.2], sources: [hvr-ghc]}}
31
27
32− - env: BUILD=cabal CABALVER=2.2 GHCVER=8.4.2
28+ - env: BUILD=hlint CABALVER=2.2 GHCVER=8.4.4
29+ compiler: ": #GHC 8.4.4"
30+ addons: {apt: {packages: [cabal-install-2.2,ghc-8.4.4], sources: [hvr-ghc]}}
31+
32+ - env: BUILD=stack CABALVER=2.4 GHCVER=8.6.5 STACK_YAML=stack-bench.yaml
33− compiler: ": #GHC 8.4.2"
33+ compiler: ": #GHC 8.6.5"
34+ addons: {apt: {packages: [cabal-install-2.4,ghc-8.6.5], sources: [hvr-ghc]}}
35+
36+ - env: BUILD=stack CABALVER=2.2 GHCVER=8.6.5 STACK_YAML=stack-ffi-unescape.yaml
37+ compiler: ": #GHC 8.6.5"
34− addons: {apt: {packages: [cabal-install-2.2,ghc-8.4.2], sources: [hvr-ghc]}}
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"
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+
35 - env: BUILD=cabal CABALVER=2.0 GHCVER=8.2.2
52 - env: BUILD=cabal CABALVER=2.0 GHCVER=8.2.2
36 compiler: ": #GHC 8.2.2"
53 compiler: ": #GHC 8.2.2"
37 addons: {apt: {packages: [cabal-install-2.0,ghc-8.2.2], sources: [hvr-ghc]}}
54 addons: {apt: {packages: [cabal-install-2.0,ghc-8.2.2], sources: [hvr-ghc]}}
55+
38 - env: BUILD=cabal CABALVER=2.0 GHCVER=8.0.2
56 - env: BUILD=cabal CABALVER=2.0 GHCVER=8.0.2
39 compiler: ": #GHC 8.0.2"
57 compiler: ": #GHC 8.0.2"
40 addons: {apt: {packages: [cabal-install-2.0,ghc-8.0.2], sources: [hvr-ghc]}}
58 addons: {apt: {packages: [cabal-install-2.0,ghc-8.0.2], sources: [hvr-ghc]}}
59+
41 - env: BUILD=cabal CABALVER=1.22 GHCVER=7.10.3
60 - env: BUILD=cabal CABALVER=1.22 GHCVER=7.10.3
42 compiler: ": #GHC 7.10.3"
61 compiler: ": #GHC 7.10.3"
43 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]}}
63+
44 - env: BUILD=cabal CABALVER=1.18 GHCVER=7.8.4
64 - env: BUILD=cabal CABALVER=1.18 GHCVER=7.8.4
45 compiler: ": #GHC 7.8.4"
65 compiler: ": #GHC 7.8.4"
46 addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}}
66 addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}}
47− - env: BUILD=cabal CABALVER=1.18 GHCVER=7.6.3
48− compiler: ": #GHC 7.6.3"
49− addons: {apt: {packages: [cabal-install-1.18,ghc-7.6.3], sources: [hvr-ghc]}}
50− - env: BUILD=cabal CABALVER=1.18 GHCVER=7.4.2
51− compiler: ": #GHC 7.4.2"
52− addons: {apt: {packages: [cabal-install-1.18,ghc-7.4.2], sources: [hvr-ghc]}}
53
67
54before_install:
68before_install:
55 - unset CC
69 - unset CC
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+14−2
@@ -63,11 +63,18 @@ module Data.Aeson
63 , fromJSON
63 , fromJSON
64 , ToJSON(..)
64 , ToJSON(..)
65 , KeyValue(..)
65 , KeyValue(..)
66+ , (<?>)
67+ , JSONPath
66 -- ** Keys for maps
68 -- ** Keys for maps
67 , ToJSONKey(..)
69 , ToJSONKey(..)
68 , ToJSONKeyFunction(..)
70 , ToJSONKeyFunction(..)
69 , FromJSONKey(..)
71 , FromJSONKey(..)
70 , FromJSONKeyFunction(..)
72 , FromJSONKeyFunction(..)
73+ -- *** Generic keys
74+ , GToJSONKey()
75+ , genericToJSONKey
76+ , GFromJSONKey()
77+ , genericFromJSONKey
71 -- ** Liftings to unary and binary type constructors
78 -- ** Liftings to unary and binary type constructors
72 , FromJSON1(..)
79 , FromJSON1(..)
73 , parseJSON1
80 , parseJSON1
@@ -109,6 +116,10 @@ module Data.Aeson
109 , SumEncoding(..)
116 , SumEncoding(..)
110 , camelTo2
117 , camelTo2
111 , defaultTaggedObject
118 , defaultTaggedObject
119+ -- ** Options for object keys
120+ , JSONKeyOptions
121+ , keyModifier
122+ , defaultJSONKeyOptions
112
123
113 -- * Inspecting @'Value's@
124 -- * Inspecting @'Value's@
114 , withObject
125 , withObject
@@ -129,15 +140,16 @@ module Data.Aeson
129 -- * Parsing
140 -- * Parsing
130 , json
141 , json
131 , json'
142 , json'
143+ , parseIndexedJSON
132 ) where
144 ) where
133
145
134import Prelude.Compat
146import Prelude.Compat
135
147
136−import Data.Aeson.Types.FromJSON (ifromJSON)
148+import Data.Aeson.Types.FromJSON (ifromJSON, parseIndexedJSON)
137import Data.Aeson.Encoding (encodingToLazyByteString)
149import Data.Aeson.Encoding (encodingToLazyByteString)
138import 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')
139import Data.Aeson.Types
151import Data.Aeson.Types
140−import Data.Aeson.Types.Internal (JSONPath, formatError)
152+import Data.Aeson.Types.Internal (JSONPath, formatError, (<?>))
141import qualified Data.ByteString as B
153import qualified Data.ByteString as B
142import qualified Data.ByteString.Lazy as L
154import qualified Data.ByteString.Lazy as L
143
155
Data/Aeson/Compat.hsdeleted−16
@@ -1,16 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−
3−module Data.Aeson.Compat
4− (
5− fromStrict
6− ) where
7−
8−import qualified Data.ByteString as S
9−import qualified Data.ByteString.Lazy as L
10−
11−fromStrict :: S.ByteString -> L.ByteString
12−#if MIN_VERSION_bytestring(0, 9, 2)
13−fromStrict = L.fromChunks . (:[])
14−#else
15−fromStrict = L.fromStrict
16−#endif
Data/Aeson/Parser.hs+11−1
@@ -35,10 +35,20 @@ module Data.Aeson.Parser
35 , value
35 , value
36 , jstring
36 , jstring
37 , scientific
37 , scientific
38+ -- ** Handling objects with duplicate keys
39+ , jsonWith
40+ , jsonLast
41+ , jsonAccum
42+ , jsonNoDup
38 -- * Strict parsers
43 -- * Strict parsers
39 -- $strict
44 -- $strict
40 , json'
45 , json'
41 , value'
46 , value'
47+ -- ** Handling objects with duplicate keys
48+ , jsonWith'
49+ , jsonLast'
50+ , jsonAccum'
51+ , jsonNoDup'
42 -- * Decoding without FromJSON instances
52 -- * Decoding without FromJSON instances
43 , decodeWith
53 , decodeWith
44 , decodeStrictWith
54 , decodeStrictWith
@@ -47,7 +57,7 @@ module Data.Aeson.Parser
47 ) where
57 ) where
48
58
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+218−49
@@ -5,6 +5,10 @@
5#if MIN_VERSION_ghc_prim(0,3,1)
5#if MIN_VERSION_ghc_prim(0,3,1)
6{-# LANGUAGE MagicHash #-}
6{-# LANGUAGE MagicHash #-}
7#endif
7#endif
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,29 +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
38import Prelude.Compat
53import Prelude.Compat
39
54
40import Control.Applicative ((<|>))
55import Control.Applicative ((<|>))
41import Control.Monad (void, when)
56import Control.Monad (void, when)
42−import Data.Aeson.Types.Internal (IResult(..), JSONPath, Result(..), Value(..))
57+import Data.Aeson.Types.Internal (IResult(..), JSONPath, Object, Result(..), Value(..))
43import 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 (($>))
44import Data.Scientific (Scientific)
61import Data.Scientific (Scientific)
45import Data.Text (Text)
62import Data.Text (Text)
63+import Data.Vector (Vector)
46−import Data.Vector as Vector (Vector, empty, fromListN, reverse)
64+import qualified Data.Vector as Vector (empty, fromList, fromListN, reverse)
47import qualified Data.Attoparsec.ByteString as A
65import qualified Data.Attoparsec.ByteString as A
48import qualified Data.Attoparsec.Lazy as L
66import qualified Data.Attoparsec.Lazy as L
49import qualified Data.ByteString as B
67import qualified Data.ByteString as B
@@ -75,7 +93,7 @@ import qualified Data.Text.Encoding as TE
75#define C_n 110
93#define C_n 110
76#define C_t 116
94#define C_t 116
77
95
78−-- | Parse a top-level JSON value.
96+-- | Parse any JSON value.
79--
97--
80-- 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
81-- until the Haskell value is needed. This may improve performance if
99-- until the Haskell value is needed. This may improve performance if
@@ -85,10 +103,15 @@ import qualified Data.Text.Encoding as TE
85-- 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
86-- parsed only object or array types, in conformance with the
104-- parsed only object or array types, in conformance with the
87-- 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'.
88json :: Parser Value
111json :: Parser Value
89json = value
112json = value
90
113
91−-- | Parse a top-level JSON value.
114+-- | Parse any JSON value.
92--
115--
93-- 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
94-- during parsing; it performs all conversions immediately. Prefer
117-- during parsing; it performs all conversions immediately. Prefer
@@ -97,23 +120,35 @@ json = value
97-- 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
98-- parsed only object or array types, in conformance with the
121-- parsed only object or array types, in conformance with the
99-- 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''.
100json' :: Parser Value
128json' :: Parser Value
101json' = value'
129json' = value'
102
130
103−object_ :: Parser Value
131+-- Open recursion: object_, object_', array_, array_' are parameterized by the
104−object_ = {-# SCC "object_" #-} Object <$> objectValues jstring value
132+-- toplevel Value parser to be called recursively, to keep the parameter
133+-- mkObject outside of the recursive loop for proper inlining.
105
134
106−object_' :: Parser Value
135+object_ :: ([(Text, Value)] -> Either String Object) -> Parser Value -> Parser Value
136+object_ mkObject val = {-# SCC "object_" #-} Object <$> objectValues mkObject jstring val
137+{-# INLINE object_ #-}
138+
139+object_' :: ([(Text, Value)] -> Either String Object) -> Parser Value -> Parser Value
107−object_' = {-# SCC "object_'" #-} do
140+object_' mkObject val' = {-# SCC "object_'" #-} do
108− !vals <- objectValues jstring' value'
141+ !vals <- objectValues mkObject jstring' val'
109 return (Object vals)
142 return (Object vals)
110 where
143 where
111 jstring' = do
144 jstring' = do
112 !s <- jstring
145 !s <- jstring
113 return s
146 return s
147+{-# INLINE object_' #-}
114
148
149+objectValues :: ([(Text, Value)] -> Either String Object)
115−objectValues :: Parser Text -> Parser Value -> Parser (H.HashMap Text Value)
150+ -> Parser Text -> Parser Value -> Parser (H.HashMap Text Value)
116−objectValues str val = do
151+objectValues mkObject str val = do
117 skipSpace
152 skipSpace
118 w <- A.peekWord8'
153 w <- A.peekWord8'
119 if w == CLOSE_CURLY
154 if w == CLOSE_CURLY
@@ -123,22 +158,26 @@ objectValues str val = do
123 -- 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'
124 -- 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'!
125 loop acc = do
160 loop acc = do
126− k <- str <* skipSpace <* char ':'
161+ k <- (str A.<?> "object key") <* skipSpace <* (char ':' A.<?> "':'")
127− v <- val <* skipSpace
162+ v <- (val A.<?> "object value") <* skipSpace
128− ch <- A.satisfy $ \w -> w == COMMA || w == CLOSE_CURLY
163+ ch <- A.satisfy (\w -> w == COMMA || w == CLOSE_CURLY) A.<?> "',' or '}'"
129 let acc' = (k, v) : acc
164 let acc' = (k, v) : acc
130 if ch == COMMA
165 if ch == COMMA
131 then skipSpace >> loop acc'
166 then skipSpace >> loop acc'
132− else return (H.fromList acc')
167+ else case mkObject acc' of
168+ Left err -> fail err
169+ Right obj -> pure obj
133{-# INLINE objectValues #-}
170{-# INLINE objectValues #-}
134
171
135−array_ :: Parser Value
172+array_ :: Parser Value -> Parser Value
136−array_ = {-# SCC "array_" #-} Array <$> arrayValues value
173+array_ val = {-# SCC "array_" #-} Array <$> arrayValues val
174+{-# INLINE array_ #-}
137
175
138−array_' :: Parser Value
176+array_' :: Parser Value -> Parser Value
139−array_' = {-# SCC "array_'" #-} do
177+array_' val = {-# SCC "array_'" #-} do
140− !vals <- arrayValues value'
178+ !vals <- arrayValues val
141 return (Array vals)
179 return (Array vals)
180+{-# INLINE array_' #-}
142
181
143arrayValues :: Parser Value -> Parser (Vector Value)
182arrayValues :: Parser Value -> Parser (Vector Value)
144arrayValues val = do
183arrayValues val = do
@@ -149,57 +188,133 @@ arrayValues val = do
149 else loop [] 1
188 else loop [] 1
150 where
189 where
151 loop acc !len = do
190 loop acc !len = do
152− v <- val <* skipSpace
191+ v <- (val A.<?> "json list value") <* skipSpace
153− ch <- A.satisfy $ \w -> w == COMMA || w == CLOSE_SQUARE
192+ ch <- A.satisfy (\w -> w == COMMA || w == CLOSE_SQUARE) A.<?> "',' or ']'"
154 if ch == COMMA
193 if ch == COMMA
155 then skipSpace >> loop (v:acc) (len+1)
194 then skipSpace >> loop (v:acc) (len+1)
156 else return (Vector.reverse (Vector.fromListN len (v:acc)))
195 else return (Vector.reverse (Vector.fromListN len (v:acc)))
157{-# INLINE arrayValues #-}
196{-# INLINE arrayValues #-}
158
197
159−-- | Parse any JSON value. You should usually 'json' in preference to
198+-- | Parse any JSON value. Synonym of 'json'.
160−-- this function, as this function relaxes the object-or-array
161−-- requirement of RFC 4627.
162−--
163−-- In particular, be careful in using this function if you think your
164−-- code might interoperate with Javascript. A na&#xef;ve Javascript
165−-- library that parses JSON data using @eval@ is vulnerable to attack
166−-- unless the encoded data represents an object or an array. JSON
167−-- implementations in other languages conform to that same restriction
168−-- to preserve interoperability and security.
169value :: Parser Value
199value :: Parser Value
170−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
171 skipSpace
237 skipSpace
172 w <- A.peekWord8'
238 w <- A.peekWord8'
173 case w of
239 case w of
174 DOUBLE_QUOTE -> A.anyWord8 *> (String <$> jstring_)
240 DOUBLE_QUOTE -> A.anyWord8 *> (String <$> jstring_)
175− OPEN_CURLY -> A.anyWord8 *> object_
241+ OPEN_CURLY -> A.anyWord8 *> object_ mkObject value_
176− OPEN_SQUARE -> A.anyWord8 *> array_
242+ OPEN_SQUARE -> A.anyWord8 *> array_ value_
177− C_f -> string "false" *> pure (Bool False)
243+ C_f -> string "false" $> Bool False
178− C_t -> string "true" *> pure (Bool True)
244+ C_t -> string "true" $> Bool True
179− C_n -> string "null" *> pure Null
245+ C_n -> string "null" $> Null
180 _ | w >= 48 && w <= 57 || w == 45
246 _ | w >= 48 && w <= 57 || w == 45
181 -> Number <$> scientific
247 -> Number <$> scientific
182 | 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))
183
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+
184−-- | Strict version of 'value'. See also 'json''.
281+-- | Strict version of 'value'. Synonym of 'json''.
185value' :: Parser Value
282value' :: Parser Value
186−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
187 skipSpace
288 skipSpace
188 w <- A.peekWord8'
289 w <- A.peekWord8'
189 case w of
290 case w of
190 DOUBLE_QUOTE -> do
291 DOUBLE_QUOTE -> do
191 !s <- A.anyWord8 *> jstring_
292 !s <- A.anyWord8 *> jstring_
192 return (String s)
293 return (String s)
193− OPEN_CURLY -> A.anyWord8 *> object_'
294+ OPEN_CURLY -> A.anyWord8 *> object_' mkObject value_
194− OPEN_SQUARE -> A.anyWord8 *> array_'
295+ OPEN_SQUARE -> A.anyWord8 *> array_' value_
195− C_f -> string "false" *> pure (Bool False)
296+ C_f -> string "false" $> Bool False
196− C_t -> string "true" *> pure (Bool True)
297+ C_t -> string "true" $> Bool True
197− C_n -> string "null" *> pure Null
298+ C_n -> string "null" $> Null
198 _ | w >= 48 && w <= 57 || w == 45
299 _ | w >= 48 && w <= 57 || w == 45
199 -> do
300 -> do
200 !n <- scientific
301 !n <- scientific
201 return (Number n)
302 return (Number n)
202 | 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
203
318
204-- | Parse a quoted JSON string.
319-- | Parse a quoted JSON string.
205jstring :: Parser Text
320jstring :: Parser Text
@@ -275,7 +390,12 @@ eitherDecodeWith p to s =
275 L.Done _ v -> case to v of
390 L.Done _ v -> case to v of
276 ISuccess a -> Right a
391 ISuccess a -> Right a
277 IError path msg -> Left (path, msg)
392 IError path msg -> Left (path, msg)
278− 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
279{-# INLINE eitherDecodeWith #-}
399{-# INLINE eitherDecodeWith #-}
280
400
281eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
401eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
@@ -328,12 +448,11 @@ data SP = SP !Integer {-# UNPACK #-}!Int
328
448
329decimal0 :: Parser Integer
449decimal0 :: Parser Integer
330decimal0 = do
450decimal0 = do
331− let step a w = a * 10 + fromIntegral (w - zero)
332− zero = 48
451+ let zero = 48
333 digits <- A.takeWhile1 isDigit_w8
452 digits <- A.takeWhile1 isDigit_w8
334 if B.length digits > 1 && B.unsafeHead digits == zero
453 if B.length digits > 1 && B.unsafeHead digits == zero
335 then fail "leading zero"
454 then fail "leading zero"
336− else return (B.foldl' step 0 digits)
455+ else return (bsToInteger digits)
337
456
338-- | Parse a JSON number.
457-- | Parse a JSON number.
339scientific :: Parser Scientific
458scientific :: Parser Scientific
@@ -366,3 +485,53 @@ scientific = do
366 fmap (Sci.scientific signedCoeff . (e +)) (signed decimal)) <|>
485 fmap (Sci.scientific signedCoeff . (e +)) (signed decimal)) <|>
367 return (Sci.scientific signedCoeff e)
486 return (Sci.scientific signedCoeff e)
368{-# 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/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+52−60
@@ -135,22 +135,15 @@ import Data.Map (Map)
135import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
135import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
136import qualified Data.Monoid as Monoid
136import 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)
151import qualified Data.Aeson.Encoding.Internal as E
144import qualified Data.Aeson.Encoding.Internal as E
152import qualified Data.Foldable as F (all)
145import qualified Data.Foldable as F (all)
153−import qualified Data.HashMap.Strict as H (lookup, toList)
146+import qualified Data.HashMap.Strict as H (difference, fromList, keys, lookup, toList)
154import qualified Data.List.NonEmpty as NE (length, reverse)
147import qualified Data.List.NonEmpty as NE (length, reverse)
155import qualified Data.Map as M (fromList, keys, lookup , singleton, size)
148import qualified Data.Map as M (fromList, keys, lookup , singleton, size)
156import qualified Data.Semigroup as Semigroup (Option(..))
149import qualified Data.Semigroup as Semigroup (Option(..))
@@ -159,8 +152,6 @@ import qualified Data.Text as T (Text, pack, unpack)
159import qualified Data.Vector as V (unsafeIndex, null, length, create, empty)
152import 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−{-# ANN module "Hlint: ignore Reduce duplication" #-}
163−
164--------------------------------------------------------------------------------
155--------------------------------------------------------------------------------
165-- Convenience
156-- Convenience
166--------------------------------------------------------------------------------
157--------------------------------------------------------------------------------
@@ -334,13 +325,13 @@ consToValue :: ToJSONFun
334consToValue _ _ _ _ [] = error $ "Data.Aeson.TH.consToValue: "
325consToValue _ _ _ _ [] = error $ "Data.Aeson.TH.consToValue: "
335 ++ "Not a single constructor given!"
326 ++ "Not a single constructor given!"
336
327
337−consToValue target jc opts vars cons = do
328+consToValue target jc opts instTys cons = do
338 value <- newName "value"
329 value <- newName "value"
339 tjs <- newNameList "_tj" $ arityInt jc
330 tjs <- newNameList "_tj" $ arityInt jc
340 tjls <- newNameList "_tjl" $ arityInt jc
331 tjls <- newNameList "_tjl" $ arityInt jc
341 let zippedTJs = zip tjs tjls
332 let zippedTJs = zip tjs tjls
342 interleavedTJs = interleave tjs tjls
333 interleavedTJs = interleave tjs tjls
343− lastTyVars = map varTToName $ drop (length vars - arityInt jc) vars
334+ lastTyVars = map varTToName $ drop (length instTys - arityInt jc) instTys
344 tvMap = M.fromList $ zip lastTyVars zippedTJs
335 tvMap = M.fromList $ zip lastTyVars zippedTJs
345 lamE (map varP $ interleavedTJs ++ [value]) $
336 lamE (map varP $ interleavedTJs ++ [value]) $
346 caseE (varE value) (matches tvMap)
337 caseE (varE value) (matches tvMap)
@@ -672,13 +663,13 @@ consFromJSON :: JSONClass
672consFromJSON _ _ _ _ [] = error $ "Data.Aeson.TH.consFromJSON: "
663consFromJSON _ _ _ _ [] = error $ "Data.Aeson.TH.consFromJSON: "
673 ++ "Not a single constructor given!"
664 ++ "Not a single constructor given!"
674
665
675−consFromJSON jc tName opts vars cons = do
666+consFromJSON jc tName opts instTys cons = do
676 value <- newName "value"
667 value <- newName "value"
677 pjs <- newNameList "_pj" $ arityInt jc
668 pjs <- newNameList "_pj" $ arityInt jc
678 pjls <- newNameList "_pjl" $ arityInt jc
669 pjls <- newNameList "_pjl" $ arityInt jc
679 let zippedPJs = zip pjs pjls
670 let zippedPJs = zip pjs pjls
680 interleavedPJs = interleave pjs pjls
671 interleavedPJs = interleave pjs pjls
681− lastTyVars = map varTToName $ drop (length vars - arityInt jc) vars
672+ lastTyVars = map varTToName $ drop (length instTys - arityInt jc) instTys
682 tvMap = M.fromList $ zip lastTyVars zippedPJs
673 tvMap = M.fromList $ zip lastTyVars zippedPJs
683 lamE (map varP $ interleavedPJs ++ [value]) $ lamExpr value tvMap
674 lamE (map varP $ interleavedPJs ++ [value]) $ lamExpr value tvMap
684
675
@@ -931,12 +922,32 @@ parseRecord :: JSONClass
931 -> Name
922 -> Name
932 -> [Name]
923 -> [Name]
933 -> Name
924 -> Name
925+ -> Bool
934 -> ExpQ
926 -> ExpQ
935−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) $
936 foldl' (\a b -> infixApp a [|(<*>)|] b)
931 foldl' (\a b -> infixApp a [|(<*>)|] b)
937 (infixApp (conE conName) [|(<$>)|] x)
932 (infixApp (conE conName) [|(<$>)|] x)
938 xs
933 xs
939 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+ ]
940 x:xs = [ [|lookupField|]
951 x:xs = [ [|lookupField|]
941 `appE` dispatchParseJSON jc conName tvMap argTy
952 `appE` dispatchParseJSON jc conName tvMap argTy
942 `appE` litE (stringL $ show tName)
953 `appE` litE (stringL $ show tName)
@@ -1011,7 +1022,7 @@ parseArgs jc tvMap tName opts
1011 , constructorFields = argTys }
1022 , constructorFields = argTys }
1012 (Left (_, obj)) = do
1023 (Left (_, obj)) = do
1013 argTys' <- mapM resolveTypeSynonyms argTys
1024 argTys' <- mapM resolveTypeSynonyms argTys
1014− parseRecord jc tvMap argTys' opts tName conName fields obj
1025+ parseRecord jc tvMap argTys' opts tName conName fields obj True
1015parseArgs jc tvMap tName opts
1026parseArgs jc tvMap tName opts
1016 info@ConstructorInfo { constructorName = conName
1027 info@ConstructorInfo { constructorName = conName
1017 , constructorVariant = RecordConstructor fields
1028 , constructorVariant = RecordConstructor fields
@@ -1026,7 +1037,7 @@ parseArgs jc tvMap tName opts
1026 argTys' <- mapM resolveTypeSynonyms argTys
1037 argTys' <- mapM resolveTypeSynonyms argTys
1027 caseE (varE valName)
1038 caseE (varE valName)
1028 [ match (conP 'Object [varP obj]) (normalB $
1039 [ match (conP 'Object [varP obj]) (normalB $
1029− parseRecord jc tvMap argTys' opts tName conName fields obj) []
1040+ parseRecord jc tvMap argTys' opts tName conName fields obj False) []
1030 , matchFailed tName conName "Object"
1041 , matchFailed tName conName "Object"
1031 ]
1042 ]
1032
1043
@@ -1209,23 +1220,27 @@ deriveJSONClass :: [(JSONFun, JSONClass -> Name -> Options -> [Type]
1209deriveJSONClass consFuns jc opts name = do
1220deriveJSONClass consFuns jc opts name = do
1210 info <- reifyDatatype name
1221 info <- reifyDatatype name
1211 case info of
1222 case info of
1212− DatatypeInfo { datatypeContext = ctxt
1223+ DatatypeInfo { datatypeContext = ctxt
1213− , datatypeName = parentName
1224+ , datatypeName = parentName
1225+#if MIN_VERSION_th_abstraction(0,3,0)
1226+ , datatypeInstTypes = instTys
1227+#else
1214− , datatypeVars = vars
1228+ , datatypeVars = instTys
1229+#endif
1215− , datatypeVariant = variant
1230+ , datatypeVariant = variant
1216− , datatypeCons = cons
1231+ , datatypeCons = cons
1217 } -> do
1232 } -> do
1218 (instanceCxt, instanceType)
1233 (instanceCxt, instanceType)
1219− <- buildTypeInstance parentName jc ctxt vars variant
1234+ <- buildTypeInstance parentName jc ctxt instTys variant
1220 (:[]) <$> instanceD (return instanceCxt)
1235 (:[]) <$> instanceD (return instanceCxt)
1221 (return instanceType)
1236 (return instanceType)
1222− (methodDecs parentName vars cons)
1237+ (methodDecs parentName instTys cons)
1223 where
1238 where
1224 methodDecs :: Name -> [Type] -> [ConstructorInfo] -> [Q Dec]
1239 methodDecs :: Name -> [Type] -> [ConstructorInfo] -> [Q Dec]
1225− methodDecs parentName vars cons = flip map consFuns $ \(jf, jfMaker) ->
1240+ methodDecs parentName instTys cons = flip map consFuns $ \(jf, jfMaker) ->
1226 funD (jsonFunValName jf (arity jc))
1241 funD (jsonFunValName jf (arity jc))
1227 [ clause []
1242 [ clause []
1228− (normalB $ jfMaker jc parentName opts vars cons)
1243+ (normalB $ jfMaker jc parentName opts instTys cons)
1229 []
1244 []
1230 ]
1245 ]
1231
1246
@@ -1241,17 +1256,21 @@ mkFunCommon :: (JSONClass -> Name -> Options -> [Type] -> [ConstructorInfo] -> Q
1241mkFunCommon consFun jc opts name = do
1256mkFunCommon consFun jc opts name = do
1242 info <- reifyDatatype name
1257 info <- reifyDatatype name
1243 case info of
1258 case info of
1244− DatatypeInfo { datatypeContext = ctxt
1259+ DatatypeInfo { datatypeContext = ctxt
1245− , datatypeName = parentName
1260+ , datatypeName = parentName
1261+#if MIN_VERSION_th_abstraction(0,3,0)
1262+ , datatypeInstTypes = instTys
1263+#else
1246− , datatypeVars = vars
1264+ , datatypeVars = instTys
1265+#endif
1247− , datatypeVariant = variant
1266+ , datatypeVariant = variant
1248− , datatypeCons = cons
1267+ , datatypeCons = cons
1249 } -> do
1268 } -> do
1250 -- We force buildTypeInstance here since it performs some checks for whether
1269 -- We force buildTypeInstance here since it performs some checks for whether
1251 -- 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
1252 -- typeclass, and produces errors if it can't.
1271 -- typeclass, and produces errors if it can't.
1253− !_ <- buildTypeInstance parentName jc ctxt vars variant
1272+ !_ <- buildTypeInstance parentName jc ctxt instTys variant
1254− consFun jc parentName opts vars cons
1273+ consFun jc parentName opts instTys cons
1255
1274
1256dispatchFunByType :: JSONClass
1275dispatchFunByType :: JSONClass
1257 -> JSONFun
1276 -> JSONFun
@@ -1584,21 +1603,13 @@ type TyVarMap = Map Name (Name, Name)
1584-- | Returns True if a Type has kind *.
1603-- | Returns True if a Type has kind *.
1585hasKindStar :: Type -> Bool
1604hasKindStar :: Type -> Bool
1586hasKindStar VarT{} = True
1605hasKindStar VarT{} = True
1587−#if MIN_VERSION_template_haskell(2,8,0)
1588hasKindStar (SigT _ StarT) = True
1606hasKindStar (SigT _ StarT) = True
1589−#else
1590−hasKindStar (SigT _ StarK) = True
1591−#endif
1592hasKindStar _ = False
1607hasKindStar _ = False
1593
1608
1594-- 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.
1595isStarOrVar :: Kind -> Bool
1610isStarOrVar :: Kind -> Bool
1596−#if MIN_VERSION_template_haskell(2,8,0)
1597isStarOrVar StarT = True
1611isStarOrVar StarT = True
1598isStarOrVar VarT{} = True
1612isStarOrVar VarT{} = True
1599−#else
1600−isStarOrVar StarK = True
1601−#endif
1602isStarOrVar _ = False
1613isStarOrVar _ = False
1603
1614
1604-- 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.
@@ -1656,9 +1667,7 @@ isTyFamily (ConT n) = do
1656#else
1667#else
1657 FamilyI (FamilyD TypeFam _ _ _) _ -> True
1668 FamilyI (FamilyD TypeFam _ _ _) _ -> True
1658#endif
1669#endif
1659−#if MIN_VERSION_template_haskell(2,9,0)
1660 FamilyI ClosedTypeFamilyD{} _ -> True
1670 FamilyI ClosedTypeFamilyD{} _ -> True
1661−#endif
1662 _ -> False
1671 _ -> False
1663isTyFamily _ = return False
1672isTyFamily _ = return False
1664
1673
@@ -1686,9 +1695,7 @@ mentionsName = go
1686 go :: Type -> [Name] -> Bool
1695 go :: Type -> [Name] -> Bool
1687 go (AppT t1 t2) names = go t1 names || go t2 names
1696 go (AppT t1 t2) names = go t1 names || go t2 names
1688 go (SigT t _k) names = go t names
1697 go (SigT t _k) names = go t names
1689−#if MIN_VERSION_template_haskell(2,8,0)
1690 || go _k names
1698 || go _k names
1691−#endif
1692 go (VarT n) names = n `elem` names
1699 go (VarT n) names = n `elem` names
1693 go _ _ = False
1700 go _ _ = False
1694
1701
@@ -1744,23 +1751,14 @@ uncurryTy t = ([], t :| [])
1744
1751
1745-- | Like uncurryType, except on a kind level.
1752-- | Like uncurryType, except on a kind level.
1746uncurryKind :: Kind -> NonEmpty Kind
1753uncurryKind :: Kind -> NonEmpty Kind
1747−#if MIN_VERSION_template_haskell(2,8,0)
1748uncurryKind = snd . uncurryTy
1754uncurryKind = snd . uncurryTy
1749−#else
1750−uncurryKind (ArrowK k1 k2) = k1 <| uncurryKind k2
1751−uncurryKind k = k :| []
1752−#endif
1753
1755
1754createKindChain :: Int -> Kind
1756createKindChain :: Int -> Kind
1755createKindChain = go starK
1757createKindChain = go starK
1756 where
1758 where
1757 go :: Kind -> Int -> Kind
1759 go :: Kind -> Int -> Kind
1758 go k 0 = k
1760 go k 0 = k
1759−#if MIN_VERSION_template_haskell(2,8,0)
1760 go k !n = go (AppT (AppT ArrowT StarT) k) (n - 1)
1761 go k !n = go (AppT (AppT ArrowT StarT) k) (n - 1)
1761−#else
1762− go k !n = go (ArrowK StarK k) (n - 1)
1763−#endif
1764
1762
1765-- | Makes a string literal expression from a constructor's name.
1763-- | Makes a string literal expression from a constructor's name.
1766conNameExp :: Options -> ConstructorInfo -> Q Exp
1764conNameExp :: Options -> ConstructorInfo -> Q Exp
@@ -1814,11 +1812,7 @@ canEtaReduce remaining dropped =
1814-------------------------------------------------------------------------------
1812-------------------------------------------------------------------------------
1815
1813
1816applySubstitutionKind :: Map Name Kind -> Type -> Type
1814applySubstitutionKind :: Map Name Kind -> Type -> Type
1817−#if MIN_VERSION_template_haskell(2,8,0)
1818applySubstitutionKind = applySubstitution
1815applySubstitutionKind = applySubstitution
1819−#else
1820−applySubstitutionKind _ t = t
1821−#endif
1822
1816
1823substNameWithKind :: Name -> Kind -> Type -> Type
1817substNameWithKind :: Name -> Kind -> Type -> Type
1824substNameWithKind n k = applySubstitutionKind (M.singleton n k)
1818substNameWithKind n k = applySubstitutionKind (M.singleton n k)
@@ -1982,9 +1976,7 @@ data StarKindStatus = NotKindStar
1982canRealizeKindStar :: Type -> StarKindStatus
1976canRealizeKindStar :: Type -> StarKindStatus
1983canRealizeKindStar t = case t of
1977canRealizeKindStar t = case t of
1984 _ | hasKindStar t -> KindStar
1978 _ | hasKindStar t -> KindStar
1985−#if MIN_VERSION_template_haskell(2,8,0)
1986 SigT _ (VarT k) -> IsKindVar k
1979 SigT _ (VarT k) -> IsKindVar k
1987−#endif
1988 _ -> NotKindStar
1980 _ -> NotKindStar
1989
1981
1990-- | 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/Types.hs+21
@@ -26,6 +26,7 @@ module Data.Aeson.Types
26 -- * Convenience types and functions
26 -- * Convenience types and functions
27 , DotNetTime(..)
27 , DotNetTime(..)
28 , typeMismatch
28 , typeMismatch
29+ , unexpected
29 -- * Type conversion
30 -- * Type conversion
30 , Parser
31 , Parser
31 , Result(..)
32 , Result(..)
@@ -37,6 +38,7 @@ module Data.Aeson.Types
37 , ToJSON(..)
38 , ToJSON(..)
38 , KeyValue(..)
39 , KeyValue(..)
39 , modifyFailure
40 , modifyFailure
41+ , prependFailure
40 , parserThrowError
42 , parserThrowError
41 , parserCatchError
43 , parserCatchError
42
44
@@ -51,6 +53,12 @@ module Data.Aeson.Types
51 , coerceFromJSONKeyFunction
53 , coerceFromJSONKeyFunction
52 , mapFromJSONKeyFunction
54 , mapFromJSONKeyFunction
53
55
56+ -- *** Generic keys
57+ , GToJSONKey()
58+ , genericToJSONKey
59+ , GFromJSONKey()
60+ , genericFromJSONKey
61+
54 -- ** Liftings to unary and binary type constructors
62 -- ** Liftings to unary and binary type constructors
55 , FromJSON1(..)
63 , FromJSON1(..)
56 , parseJSON1
64 , parseJSON1
@@ -116,6 +124,7 @@ module Data.Aeson.Types
116 , sumEncoding
124 , sumEncoding
117 , unwrapUnaryRecords
125 , unwrapUnaryRecords
118 , tagSingleConstructors
126 , tagSingleConstructors
127+ , rejectUnknownFields
119
128
120 -- ** Options utilities
129 -- ** Options utilities
121 , SumEncoding(..)
130 , SumEncoding(..)
@@ -123,6 +132,18 @@ module Data.Aeson.Types
123 , camelTo2
132 , camelTo2
124 , defaultOptions
133 , defaultOptions
125 , 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
126 ) where
147 ) where
127
148
128import Prelude.Compat
149import Prelude.Compat
Data/Aeson/Types/Class.hs+6−1
@@ -6,7 +6,6 @@
6{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE NoImplicitPrelude #-}
7{-# LANGUAGE NoImplicitPrelude #-}
8{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE ScopedTypeVariables #-}
9−{-# LANGUAGE TypeSynonymInstances #-}
10
9
11-- |
10-- |
12-- Module: Data.Aeson.Types.Class
11-- Module: Data.Aeson.Types.Class
@@ -59,6 +58,11 @@ module Data.Aeson.Types.Class
59 , fromJSONKeyCoerce
58 , fromJSONKeyCoerce
60 , coerceFromJSONKeyFunction
59 , coerceFromJSONKeyFunction
61 , mapFromJSONKeyFunction
60 , mapFromJSONKeyFunction
61+ -- ** Generic keys
62+ , GToJSONKey()
63+ , genericToJSONKey
64+ , GFromJSONKey()
65+ , genericFromJSONKey
62 -- * Object key-value pairs
66 -- * Object key-value pairs
63 , KeyValue(..)
67 , KeyValue(..)
64
68
@@ -79,6 +83,7 @@ module Data.Aeson.Types.Class
79 , fromJSON
83 , fromJSON
80 , ifromJSON
84 , ifromJSON
81 , typeMismatch
85 , typeMismatch
86+ , unexpected
82 , parseField
87 , parseField
83 , parseFieldMaybe
88 , parseFieldMaybe
84 , parseFieldMaybe'
89 , parseFieldMaybe'
Data/Aeson/Types/FromJSON.hs+758−383
@@ -5,20 +5,16 @@
5{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GADTs #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8−{-# LANGUAGE NamedFieldPuns #-}
9{-# LANGUAGE NoImplicitPrelude #-}
8{-# LANGUAGE NoImplicitPrelude #-}
10{-# LANGUAGE OverloadedStrings #-}
9{-# LANGUAGE OverloadedStrings #-}
10+{-# LANGUAGE PolyKinds #-}
11{-# LANGUAGE RecordWildCards #-}
11{-# LANGUAGE RecordWildCards #-}
12{-# LANGUAGE ScopedTypeVariables #-}
12{-# LANGUAGE ScopedTypeVariables #-}
13{-# LANGUAGE TypeOperators #-}
13{-# LANGUAGE TypeOperators #-}
14−{-# LANGUAGE TypeSynonymInstances #-}
14+{-# LANGUAGE TupleSections #-}
15{-# LANGUAGE UndecidableInstances #-}
15{-# LANGUAGE UndecidableInstances #-}
16{-# LANGUAGE ViewPatterns #-}
16{-# LANGUAGE ViewPatterns #-}
17
17
18−#if __GLASGOW_HASKELL__ >= 706
19−{-# LANGUAGE PolyKinds #-}
20−#endif
21−
22#include "incoherent-compat.h"
18#include "incoherent-compat.h"
23#include "overlapping-compat.h"
19#include "overlapping-compat.h"
24
20
@@ -46,6 +42,9 @@ module Data.Aeson.Types.FromJSON
46 , coerceFromJSONKeyFunction
42 , coerceFromJSONKeyFunction
47 , mapFromJSONKeyFunction
43 , mapFromJSONKeyFunction
48
44
45+ , GFromJSONKey()
46+ , genericFromJSONKey
47+
49 -- * List functions
48 -- * List functions
50 , listParser
49 , listParser
51
50
@@ -61,12 +60,14 @@ module Data.Aeson.Types.FromJSON
61 , fromJSON
60 , fromJSON
62 , ifromJSON
61 , ifromJSON
63 , typeMismatch
62 , typeMismatch
63+ , unexpected
64 , parseField
64 , parseField
65 , parseFieldMaybe
65 , parseFieldMaybe
66 , parseFieldMaybe'
66 , parseFieldMaybe'
67 , explicitParseField
67 , explicitParseField
68 , explicitParseFieldMaybe
68 , explicitParseFieldMaybe
69 , explicitParseFieldMaybe'
69 , explicitParseFieldMaybe'
70+ , parseIndexedJSON
70 -- ** Operators
71 -- ** Operators
71 , (.:)
72 , (.:)
72 , (.:?)
73 , (.:?)
@@ -79,14 +80,14 @@ module Data.Aeson.Types.FromJSON
79
80
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)
85import Data.Aeson.Parser.Internal (eitherDecodeWith, jsonEOF)
86import Data.Aeson.Parser.Internal (eitherDecodeWith, jsonEOF)
86import Data.Aeson.Types.Generic
87import Data.Aeson.Types.Generic
87import Data.Aeson.Types.Internal
88import Data.Aeson.Types.Internal
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(..))
@@ -102,8 +103,10 @@ 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)
@@ -115,9 +118,9 @@ import GHC.Generics
115import Numeric.Natural (Natural)
118import Numeric.Natural (Natural)
116import Text.ParserCombinators.ReadP (readP_to_S)
119import Text.ParserCombinators.ReadP (readP_to_S)
117import Unsafe.Coerce (unsafeCoerce)
120import Unsafe.Coerce (unsafeCoerce)
118−import qualified Data.Aeson.Compat as Compat
119import qualified Data.Aeson.Parser.Time as Time
121import qualified Data.Aeson.Parser.Time as Time
120import 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
121import qualified Data.DList as DList
124import qualified Data.DList as DList
122import qualified Data.HashMap.Strict as H
125import qualified Data.HashMap.Strict as H
123import qualified Data.HashSet as HashSet
126import qualified Data.HashSet as HashSet
@@ -140,18 +143,20 @@ import qualified Data.Vector.Primitive as VP
140import qualified Data.Vector.Storable as VS
143import qualified Data.Vector.Storable as VS
141import qualified Data.Vector.Unboxed as VU
144import qualified Data.Vector.Unboxed as VU
142
145
143−#ifndef HAS_COERCIBLE
146+import qualified GHC.Exts as Exts
144−#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
145#endif
154#endif
155+import qualified Data.Primitive.PrimArray as PM
156+#endif
157+
146
158
147−#if HAS_COERCIBLE
148import Data.Coerce (Coercible, coerce)
159import Data.Coerce (Coercible, coerce)
149−coerce' :: Coercible a b => a -> b
150−coerce' = coerce
151−#else
152−coerce' :: a -> b
153−coerce' = unsafeCoerce
154−#endif
155
160
156parseIndexedJSON :: (Value -> Parser a) -> Int -> Value -> Parser a
161parseIndexedJSON :: (Value -> Parser a) -> Int -> Value -> Parser a
157parseIndexedJSON p idx value = p value <?> Index idx
162parseIndexedJSON p idx value = p value <?> Index idx
@@ -160,7 +165,7 @@ parseIndexedJSON p idx value = p value <?> Index idx
160parseIndexedJSONPair :: (Value -> Parser a) -> (Value -> Parser b) -> Int -> Value -> Parser (a, b)
165parseIndexedJSONPair :: (Value -> Parser a) -> (Value -> Parser b) -> Int -> Value -> Parser (a, b)
161parseIndexedJSONPair keyParser valParser idx value = p value <?> Index idx
166parseIndexedJSONPair keyParser valParser idx value = p value <?> Index idx
162 where
167 where
163− p = withArray "(k,v)" $ \ab ->
168+ p = withArray "(k, v)" $ \ab ->
164 let n = V.length ab
169 let n = V.length ab
165 in if n == 2
170 in if n == 2
166 then (,) <$> parseJSONElemAtIndex keyParser 0 ab
171 then (,) <$> parseJSONElemAtIndex keyParser 0 ab
@@ -173,33 +178,33 @@ parseJSONElemAtIndex :: (Value -> Parser a) -> Int -> V.Vector Value -> Parser a
173parseJSONElemAtIndex p idx ary = p (V.unsafeIndex ary idx) <?> Index idx
178parseJSONElemAtIndex p idx ary = p (V.unsafeIndex ary idx) <?> Index idx
174
179
175parseRealFloat :: RealFloat a => String -> Value -> Parser a
180parseRealFloat :: RealFloat a => String -> Value -> Parser a
176−parseRealFloat _ (Number s) = pure $ Scientific.toRealFloat s
181+parseRealFloat _ (Number s) = pure $ Scientific.toRealFloat s
177−parseRealFloat _ Null = pure (0/0)
182+parseRealFloat _ Null = pure (0/0)
178−parseRealFloat expected v = typeMismatch expected v
183+parseRealFloat name v = prependContext name (unexpected v)
179{-# INLINE parseRealFloat #-}
184{-# INLINE parseRealFloat #-}
180
185
181−parseIntegralFromScientific :: forall a. Integral a => String -> Scientific -> Parser a
186+parseIntegralFromScientific :: forall a. Integral a => Scientific -> Parser a
182−parseIntegralFromScientific expected s =
187+parseIntegralFromScientific s =
183 case Scientific.floatingOrInteger s :: Either Double a of
188 case Scientific.floatingOrInteger s :: Either Double a of
184 Right x -> pure x
189 Right x -> pure x
185− Left _ -> fail $ "expected " ++ expected ++ ", encountered floating number " ++ show s
190+ Left _ -> fail $ "unexpected floating number " ++ show s
186{-# INLINE parseIntegralFromScientific #-}
191{-# INLINE parseIntegralFromScientific #-}
187
192
188parseIntegral :: Integral a => String -> Value -> Parser a
193parseIntegral :: Integral a => String -> Value -> Parser a
189−parseIntegral expected =
194+parseIntegral name =
190− withBoundedScientific expected $ parseIntegralFromScientific expected
195+ prependContext name . withBoundedScientific' parseIntegralFromScientific
191{-# INLINE parseIntegral #-}
196{-# INLINE parseIntegral #-}
192
197
193−parseBoundedIntegralFromScientific :: (Bounded a, Integral a) => String -> Scientific -> Parser a
198+parseBoundedIntegralFromScientific :: (Bounded a, Integral a) => Scientific -> Parser a
194−parseBoundedIntegralFromScientific expected s = maybe
199+parseBoundedIntegralFromScientific s = maybe
195− (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)
196 pure
201 pure
197 (Scientific.toBoundedInteger s)
202 (Scientific.toBoundedInteger s)
198{-# INLINE parseBoundedIntegralFromScientific #-}
203{-# INLINE parseBoundedIntegralFromScientific #-}
199
204
200parseBoundedIntegral :: (Bounded a, Integral a) => String -> Value -> Parser a
205parseBoundedIntegral :: (Bounded a, Integral a) => String -> Value -> Parser a
201−parseBoundedIntegral expected =
206+parseBoundedIntegral name =
202− withScientific expected $ parseBoundedIntegralFromScientific expected
207+ prependContext name . withScientific' parseBoundedIntegralFromScientific
203{-# INLINE parseBoundedIntegral #-}
208{-# INLINE parseBoundedIntegral #-}
204
209
205parseScientificText :: Text -> Parser Scientific
210parseScientificText :: Text -> Parser Scientific
@@ -209,18 +214,20 @@ parseScientificText
209 . T.encodeUtf8
214 . T.encodeUtf8
210
215
211parseIntegralText :: Integral a => String -> Text -> Parser a
216parseIntegralText :: Integral a => String -> Text -> Parser a
212−parseIntegralText expected t
217+parseIntegralText name t =
218+ prependContext name $
213− = parseScientificText t
219+ parseScientificText t
214− >>= rejectLargeExponent
220+ >>= rejectLargeExponent
215− >>= parseIntegralFromScientific expected
221+ >>= parseIntegralFromScientific
216 where
222 where
217 rejectLargeExponent :: Scientific -> Parser Scientific
223 rejectLargeExponent :: Scientific -> Parser Scientific
218− rejectLargeExponent s = withBoundedScientific expected pure (Number s)
224+ rejectLargeExponent s = withBoundedScientific' pure (Number s)
219{-# INLINE parseIntegralText #-}
225{-# INLINE parseIntegralText #-}
220
226
221parseBoundedIntegralText :: (Bounded a, Integral a) => String -> Text -> Parser a
227parseBoundedIntegralText :: (Bounded a, Integral a) => String -> Text -> Parser a
222−parseBoundedIntegralText expected t =
228+parseBoundedIntegralText name t =
229+ prependContext name $
223− parseScientificText t >>= parseBoundedIntegralFromScientific expected
230+ parseScientificText t >>= parseBoundedIntegralFromScientific
224
231
225parseOptionalFieldWith :: (Value -> Parser (Maybe a))
232parseOptionalFieldWith :: (Value -> Parser (Maybe a))
226 -> Object -> Text -> Parser (Maybe a)
233 -> Object -> Text -> Parser (Maybe a)
@@ -280,14 +287,21 @@ genericLiftParseJSON opts pj pjl = fmap to1 . gParseJSON opts (From1Args pj pjl)
280--
287--
281-- The basic ways to signal a failed conversion are as follows:
288-- The basic ways to signal a failed conversion are as follows:
282--
289--
283−-- * 'empty' and 'mzero' work, but are terse and uninformative;
290+-- * 'fail' yields a custom error message: it is the recommended way of
291+-- reporting a failure;
284--
292--
285−-- * 'fail' yields a custom error message;
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−-- * 'typeMismatch' produces an informative message for cases when the
296+-- * 'typeMismatch' can be used to report a failure when the encountered value
288−-- value encountered is not of the expected type.
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.
289--
300--
301+-- 'prependFailure' (or 'modifyFailure') add more information to a parser's
302+-- error messages.
303+--
290−-- An example type and instance using 'typeMismatch':
304+-- An example type and instance using 'typeMismatch' and 'prependFailure':
291--
305--
292-- @
306-- @
293-- \-- Allow ourselves to write 'Text' literals.
307-- \-- Allow ourselves to write 'Text' literals.
@@ -301,13 +315,15 @@ genericLiftParseJSON opts pj pjl = fmap to1 . gParseJSON opts (From1Args pj pjl)
301-- '<*>' v '.:' \"y\"
315-- '<*>' v '.:' \"y\"
302--
316--
303-- \-- We do not expect a non-'Object' value here.
317-- \-- We do not expect a non-'Object' value here.
304−-- \-- We could use 'mzero' to fail, but 'typeMismatch'
318+-- \-- We could use 'Control.Applicative.empty' to fail, but 'typeMismatch'
305-- \-- gives a much more informative error message.
319-- \-- gives a much more informative error message.
320+-- 'parseJSON' invalid =
321+-- 'prependFailure' "parsing Coord failed, "
306−-- 'parseJSON' invalid = 'typeMismatch' \"Coord\" invalid
322+-- ('typeMismatch' \"Object\" invalid)
307-- @
323-- @
308--
324--
309-- For this common case of only being concerned with a single
325-- For this common case of only being concerned with a single
310−-- type of JSON value, the functions 'withObject', 'withNumber', etc.
326+-- type of JSON value, the functions 'withObject', 'withScientific', etc.
311-- are provided. Their use is to be preferred when possible, since
327-- are provided. Their use is to be preferred when possible, since
312-- 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
313-- (assuming the same language extension and data type) as:
329-- (assuming the same language extension and data type) as:
@@ -346,7 +362,7 @@ genericLiftParseJSON opts pj pjl = fmap to1 . gParseJSON opts (From1Args pj pjl)
346-- @
362-- @
347--
363--
348-- The default implementation will be equivalent to
364-- The default implementation will be equivalent to
349−-- @parseJSON = 'genericParseJSON' 'defaultOptions'@; If you need different
365+-- @parseJSON = 'genericParseJSON' 'defaultOptions'@; if you need different
350-- options, you can customize the generic decoding by defining:
366-- options, you can customize the generic decoding by defining:
351--
367--
352-- @
368-- @
@@ -364,13 +380,11 @@ class FromJSON a where
364 parseJSON = genericParseJSON defaultOptions
380 parseJSON = genericParseJSON defaultOptions
365
381
366 parseJSONList :: Value -> Parser [a]
382 parseJSONList :: Value -> Parser [a]
367− parseJSONList (Array a)
383+ parseJSONList = withArray "[]" $ \a ->
368− = zipWithM (parseIndexedJSON parseJSON) [0..]
384+ zipWithM (parseIndexedJSON parseJSON) [0..]
369 . V.toList
385 . V.toList
370 $ a
386 $ a
371
387
372− parseJSONList v = typeMismatch "[a]" v
373−
374-------------------------------------------------------------------------------
388-------------------------------------------------------------------------------
375-- Classes and types for map keys
389-- Classes and types for map keys
376-------------------------------------------------------------------------------
390-------------------------------------------------------------------------------
@@ -382,6 +396,16 @@ class FromJSON a where
382-- > newtype SomeId = SomeId { getSomeId :: Text }
396-- > newtype SomeId = SomeId { getSomeId :: Text }
383-- > deriving (Eq,Ord,Hashable,FromJSONKey)
397-- > deriving (Eq,Ord,Hashable,FromJSONKey)
384--
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+-- @
385class FromJSONKey a where
409class FromJSONKey a where
386 -- | Strategy for parsing the key of a map-like container.
410 -- | Strategy for parsing the key of a map-like container.
387 fromJSONKey :: FromJSONKeyFunction a
411 fromJSONKey :: FromJSONKeyFunction a
@@ -404,11 +428,7 @@ class FromJSONKey a where
404-- At the moment this type is intentionally not exported. 'FromJSONKeyFunction'
428-- At the moment this type is intentionally not exported. 'FromJSONKeyFunction'
405-- can be inspected, but cannot be constructed.
429-- can be inspected, but cannot be constructed.
406data CoerceText a where
430data CoerceText a where
407−#if HAS_COERCIBLE
408 CoerceText :: Coercible Text a => CoerceText a
431 CoerceText :: Coercible Text a => CoerceText a
409−#else
410− CoerceText :: CoerceText a
411−#endif
412
432
413-- | 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
414-- 'FromJSONKey' instance, then 'ToJSONKeyValue' should be used in the 'ToJSONKey'
434-- 'FromJSONKey' instance, then 'ToJSONKeyValue' should be used in the 'ToJSONKey'
@@ -428,7 +448,7 @@ data FromJSONKeyFunction a
428
448
429-- | Only law abiding up to interpretation
449-- | Only law abiding up to interpretation
430instance Functor FromJSONKeyFunction where
450instance Functor FromJSONKeyFunction where
431− fmap h (FromJSONKeyCoerce CoerceText) = FromJSONKeyText (h . coerce')
451+ fmap h (FromJSONKeyCoerce CoerceText) = FromJSONKeyText (h . coerce)
432 fmap h (FromJSONKeyText f) = FromJSONKeyText (h . f)
452 fmap h (FromJSONKeyText f) = FromJSONKeyText (h . f)
433 fmap h (FromJSONKeyTextParser f) = FromJSONKeyTextParser (fmap h . f)
453 fmap h (FromJSONKeyTextParser f) = FromJSONKeyTextParser (fmap h . f)
434 fmap h (FromJSONKeyValue f) = FromJSONKeyValue (fmap h . f)
454 fmap h (FromJSONKeyValue f) = FromJSONKeyValue (fmap h . f)
@@ -442,9 +462,7 @@ instance Functor FromJSONKeyFunction where
442--
462--
443-- On pre GHC 7.8 this is unconstrainted function.
463-- On pre GHC 7.8 this is unconstrainted function.
444fromJSONKeyCoerce ::
464fromJSONKeyCoerce ::
445−#if HAS_COERCIBLE
446 Coercible Text a =>
465 Coercible Text a =>
447−#endif
448 FromJSONKeyFunction a
466 FromJSONKeyFunction a
449fromJSONKeyCoerce = FromJSONKeyCoerce CoerceText
467fromJSONKeyCoerce = FromJSONKeyCoerce CoerceText
450
468
@@ -452,60 +470,91 @@ fromJSONKeyCoerce = FromJSONKeyCoerce CoerceText
452--
470--
453-- See note on 'fromJSONKeyCoerce'.
471-- See note on 'fromJSONKeyCoerce'.
454coerceFromJSONKeyFunction ::
472coerceFromJSONKeyFunction ::
455−#if HAS_COERCIBLE
456 Coercible a b =>
473 Coercible a b =>
457−#endif
458 FromJSONKeyFunction a -> FromJSONKeyFunction b
474 FromJSONKeyFunction a -> FromJSONKeyFunction b
459−#if HAS_COERCIBLE
460coerceFromJSONKeyFunction = coerce
475coerceFromJSONKeyFunction = coerce
461−#else
462−coerceFromJSONKeyFunction (FromJSONKeyCoerce CoerceText) = FromJSONKeyCoerce CoerceText
463−coerceFromJSONKeyFunction (FromJSONKeyText f) = FromJSONKeyText (coerce' . f)
464−coerceFromJSONKeyFunction (FromJSONKeyTextParser f) = FromJSONKeyTextParser (fmap coerce' . f)
465−coerceFromJSONKeyFunction (FromJSONKeyValue f) = FromJSONKeyValue (fmap coerce' . f)
466−#endif
467
476
468{-# RULES
477{-# RULES
469 "FromJSONKeyCoerce: fmap id" forall (x :: FromJSONKeyFunction a).
478 "FromJSONKeyCoerce: fmap id" forall (x :: FromJSONKeyFunction a).
470 fmap id x = x
479 fmap id x = x
471 #-}
480 #-}
472−#if HAS_COERCIBLE
473{-# RULES
481{-# RULES
474 "FromJSONKeyCoerce: fmap coerce" forall x .
482 "FromJSONKeyCoerce: fmap coerce" forall x .
475 fmap coerce x = coerceFromJSONKeyFunction x
483 fmap coerce x = coerceFromJSONKeyFunction x
476 #-}
484 #-}
477−#endif
478
485
479-- | Same as 'fmap'. Provided for the consistency with 'ToJSONKeyFunction'.
486-- | Same as 'fmap'. Provided for the consistency with 'ToJSONKeyFunction'.
480mapFromJSONKeyFunction :: (a -> b) -> FromJSONKeyFunction a -> FromJSONKeyFunction b
487mapFromJSONKeyFunction :: (a -> b) -> FromJSONKeyFunction a -> FromJSONKeyFunction b
481mapFromJSONKeyFunction = fmap
488mapFromJSONKeyFunction = fmap
482
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+
483-------------------------------------------------------------------------------
519-------------------------------------------------------------------------------
484-- Functions needed for documentation
520-- Functions needed for documentation
485-------------------------------------------------------------------------------
521-------------------------------------------------------------------------------
486
522
487-- | Fail parsing due to a type mismatch, with a descriptive message.
523-- | Fail parsing due to a type mismatch, with a descriptive message.
488--
524--
489−-- Example usage:
525+-- The following wrappers should generally be prefered:
526+-- 'withObject', 'withArray', 'withText', 'withBool'.
490--
527--
491−-- @
528+-- ==== Error message example
492−-- instance FromJSON Coord where
529+--
493−-- parseJSON ('Object' v) = {- type matches, life is good -}
530+-- > typeMismatch "Object" (String "oops")
494−-- parseJSON wat = 'typeMismatch' \"Coord\" wat
531+-- > -- Error: "expected Object, but encountered String"
495−-- @
496−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\"@).
497 -> Value -- ^ The actual value encountered.
535 -> Value -- ^ The actual value encountered.
498 -> Parser a
536 -> Parser a
499typeMismatch expected actual =
537typeMismatch expected actual =
500− fail $ "expected " ++ expected ++ ", encountered " ++ name
538+ fail $ "expected " ++ expected ++ ", but encountered " ++ typeOf actual
501− 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
502− name = case actual of
551+typeOf v = case v of
503− Object _ -> "Object"
552+ Object _ -> "Object"
504− Array _ -> "Array"
553+ Array _ -> "Array"
505− String _ -> "String"
554+ String _ -> "String"
506− Number _ -> "Number"
555+ Number _ -> "Number"
507− Bool _ -> "Boolean"
556+ Bool _ -> "Boolean"
508− Null -> "Null"
557+ Null -> "Null"
509
558
510-------------------------------------------------------------------------------
559-------------------------------------------------------------------------------
511-- Lifings of FromJSON and ToJSON to unary and binary type constructors
560-- Lifings of FromJSON and ToJSON to unary and binary type constructors
@@ -586,9 +635,8 @@ class FromJSON2 f where
586 -> (Value -> Parser b)
635 -> (Value -> Parser b)
587 -> (Value -> Parser [b])
636 -> (Value -> Parser [b])
588 -> Value -> Parser [f a b]
637 -> Value -> Parser [f a b]
589− liftParseJSONList2 fa ga fb gb v = case v of
638+ liftParseJSONList2 fa ga fb gb = withArray "[]" $ \vals ->
590− 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)
591− _ -> typeMismatch "[a]" v
592
640
593-- | Lift the standard 'parseJSON' function through the type constructor.
641-- | Lift the standard 'parseJSON' function through the type constructor.
594parseJSON2 :: (FromJSON2 f, FromJSON a, FromJSON b) => Value -> Parser (f a b)
642parseJSON2 :: (FromJSON2 f, FromJSON a, FromJSON b) => Value -> Parser (f a b)
@@ -602,7 +650,7 @@ parseJSON2 = liftParseJSON2 parseJSON parseJSONList parseJSON parseJSONList
602-- | Helper function to use with 'liftParseJSON'. See 'Data.Aeson.ToJSON.listEncoding'.
650-- | Helper function to use with 'liftParseJSON'. See 'Data.Aeson.ToJSON.listEncoding'.
603listParser :: (Value -> Parser a) -> Value -> Parser [a]
651listParser :: (Value -> Parser a) -> Value -> Parser [a]
604listParser f (Array xs) = fmap V.toList (V.mapM f xs)
652listParser f (Array xs) = fmap V.toList (V.mapM f xs)
605−listParser _ v = typeMismatch "[a]" v
653+listParser _ v = typeMismatch "Array" v
606{-# INLINE listParser #-}
654{-# INLINE listParser #-}
607
655
608-------------------------------------------------------------------------------
656-------------------------------------------------------------------------------
@@ -620,69 +668,137 @@ instance (FromJSON a) => FromJSON [a] where
620-- Functions
668-- Functions
621-------------------------------------------------------------------------------
669-------------------------------------------------------------------------------
622
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+
623−-- | @'withObject' expected f value@ applies @f@ to the 'Object' when @value@
679+-- | @'withObject' name f value@ applies @f@ to the 'Object' when @value@
624−-- 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"
625withObject :: String -> (Object -> Parser a) -> Value -> Parser a
686withObject :: String -> (Object -> Parser a) -> Value -> Parser a
626−withObject _ f (Object obj) = f obj
687+withObject _ f (Object obj) = f obj
627−withObject expected _ v = typeMismatch expected v
688+withObject name _ v = prependContext name (typeMismatch "Object" v)
628{-# INLINE withObject #-}
689{-# INLINE withObject #-}
629
690
630−-- | @'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
631−-- '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"
632withText :: String -> (Text -> Parser a) -> Value -> Parser a
698withText :: String -> (Text -> Parser a) -> Value -> Parser a
633−withText _ f (String txt) = f txt
699+withText _ f (String txt) = f txt
634−withText expected _ v = typeMismatch expected v
700+withText name _ v = prependContext name (typeMismatch "String" v)
635{-# INLINE withText #-}
701{-# INLINE withText #-}
636
702
637-- | @'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
638−-- 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"
639withArray :: String -> (Array -> Parser a) -> Value -> Parser a
710withArray :: String -> (Array -> Parser a) -> Value -> Parser a
640−withArray _ f (Array arr) = f arr
711+withArray _ f (Array arr) = f arr
641−withArray expected _ v = typeMismatch expected v
712+withArray name _ v = prependContext name (typeMismatch "Array" v)
642{-# INLINE withArray #-}
713{-# INLINE withArray #-}
643
714
644−-- | @'withScientific' expected f value@ applies @f@ to the 'Scientific' number
715+-- | @'withScientific' name f value@ applies @f@ to the 'Scientific' number
645−-- when @value@ is a 'Number' and fails using @'typeMismatch' expected@
716+-- when @value@ is a 'Data.Aeson.Number' and fails using 'typeMismatch'
646-- otherwise.
717-- otherwise.
647−-- .
718+--
648-- /Warning/: If you are converting from a scientific to an unbounded
719-- /Warning/: If you are converting from a scientific to an unbounded
649-- type such as 'Integer' you may want to add a restriction on the
720-- type such as 'Integer' you may want to add a restriction on the
650-- size of the exponent (see 'withBoundedScientific') to prevent
721-- size of the exponent (see 'withBoundedScientific') to prevent
651-- malicious input from filling up the memory of the target system.
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"
652withScientific :: String -> (Scientific -> Parser a) -> Value -> Parser a
728withScientific :: String -> (Scientific -> Parser a) -> Value -> Parser a
653−withScientific _ f (Number scientific) = f scientific
729+withScientific _ f (Number scientific) = f scientific
654−withScientific expected _ v = typeMismatch expected v
730+withScientific name _ v = prependContext name (typeMismatch "Number" v)
655{-# INLINE withScientific #-}
731{-# INLINE withScientific #-}
656
732
657−-- | @'withBoundedScientific' expected f value@ applies @f@ to the 'Scientific' number
733+-- | A variant of 'withScientific' which doesn't use 'prependContext', so that
658−-- when @value@ is a 'Number' and fails using @'typeMismatch' expected@
734+-- such context can be added separately in a way that also applies when the
659−-- otherwise.
735+-- continuation @f :: Scientific -> Parser a@ fails.
660--
736--
661−-- The conversion will also fail wyth a @'typeMismatch' if the
737+-- /Warning/: If you are converting from a scientific to an unbounded
662−-- 'Scientific' exponent is larger than 1024.
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.
663withBoundedScientific :: String -> (Scientific -> Parser a) -> Value -> Parser a
757withBoundedScientific :: String -> (Scientific -> Parser a) -> Value -> Parser a
664−withBoundedScientific _ f v@(Number scientific) =
758+withBoundedScientific name f v = withBoundedScientific_ (prependContext name) f v
665− if base10Exponent scientific > 1024
666− then typeMismatch "a number with exponent <= 1024" v
667− else f scientific
668−withBoundedScientific expected _ v = typeMismatch expected v
669{-# INLINE withBoundedScientific #-}
759{-# INLINE withBoundedScientific #-}
670
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+
671-- | @'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
672−-- '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"
673withBool :: String -> (Bool -> Parser a) -> Value -> Parser a
789withBool :: String -> (Bool -> Parser a) -> Value -> Parser a
674−withBool _ f (Bool arr) = f arr
790+withBool _ f (Bool arr) = f arr
675−withBool expected _ v = typeMismatch expected v
791+withBool name _ v = prependContext name (typeMismatch "Boolean" v)
676{-# INLINE withBool #-}
792{-# INLINE withBool #-}
677
793
678-- | Decode a nested JSON-encoded string.
794-- | Decode a nested JSON-encoded string.
679withEmbeddedJSON :: String -> (Value -> Parser a) -> Value -> Parser a
795withEmbeddedJSON :: String -> (Value -> Parser a) -> Value -> Parser a
680withEmbeddedJSON _ innerParser (String txt) =
796withEmbeddedJSON _ innerParser (String txt) =
681− either fail innerParser $ eitherDecode (Compat.fromStrict $ T.encodeUtf8 txt)
797+ either fail innerParser $ eitherDecode (L.fromStrict $ T.encodeUtf8 txt)
682 where
798 where
683 eitherDecode = eitherFormatError . eitherDecodeWith jsonEOF ifromJSON
799 eitherDecode = eitherFormatError . eitherDecodeWith jsonEOF ifromJSON
684 eitherFormatError = either (Left . uncurry formatError) Right
800 eitherFormatError = either (Left . uncurry formatError) Right
685−withEmbeddedJSON name _ v = typeMismatch name v
801+withEmbeddedJSON name _ v = prependContext name (typeMismatch "String" v)
686{-# INLINE withEmbeddedJSON #-}
802{-# INLINE withEmbeddedJSON #-}
687
803
688-- | 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.
@@ -747,7 +863,7 @@ parseFieldMaybe' = (.:!)
747-- 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)@
748explicitParseField :: (Value -> Parser a) -> Object -> Text -> Parser a
864explicitParseField :: (Value -> Parser a) -> Object -> Text -> Parser a
749explicitParseField p obj key = case H.lookup key obj of
865explicitParseField p obj key = case H.lookup key obj of
750− Nothing -> fail $ "key " ++ show key ++ " not present"
866+ Nothing -> fail $ "key " ++ show key ++ " not found"
751 Just v -> p v <?> Key key
867 Just v -> p v <?> Key key
752{-# INLINE explicitParseField #-}
868{-# INLINE explicitParseField #-}
753
869
@@ -797,6 +913,37 @@ instance OVERLAPPABLE_ (GFromJSON arity a) => GFromJSON arity (M1 i c a) where
797 -- parsed value:
913 -- parsed value:
798 gParseJSON opts fargs = fmap M1 . gParseJSON opts fargs
914 gParseJSON opts fargs = fmap M1 . gParseJSON opts fargs
799
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+
800instance (FromJSON a) => GFromJSON arity (K1 i a) where
947instance (FromJSON a) => GFromJSON arity (K1 i a) where
801 -- Constant values are decoded using their FromJSON instance:
948 -- Constant values are decoded using their FromJSON instance:
802 gParseJSON _opts _ = fmap K1 . parseJSON
949 gParseJSON _opts _ = fmap K1 . parseJSON
@@ -811,342 +958,499 @@ instance (FromJSON1 f) => GFromJSON One (Rec1 f) where
811 -- FromJSON1 instance:
958 -- FromJSON1 instance:
812 gParseJSON _opts (From1Args pj pjl) = fmap Rec1 . liftParseJSON pj pjl
959 gParseJSON _opts (From1Args pj pjl) = fmap Rec1 . liftParseJSON pj pjl
813
960
814−instance GFromJSON arity U1 where
961+instance (FromJSON1 f, GFromJSON One g) => GFromJSON One (f :.: g) where
815− -- 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:
816− gParseJSON _opts _ v
965+ gParseJSON opts fargs =
817− | isEmptyArray v = pure U1
966+ let gpj = gParseJSON opts fargs in
818− | otherwise = typeMismatch "unit constructor (U1)" v
967+ fmap Comp1 . liftParseJSON gpj (listParser gpj)
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
819
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.
820instance ( ConsFromJSON arity a
987instance ( ConsFromJSON arity a
821 , AllNullary (C1 c a) allNullary
988 , AllNullary (C1 c a) allNullary
822 , ParseSum arity (C1 c a) allNullary
989 , ParseSum arity (C1 c a) allNullary
990+ , Constructor c
823− ) => GFromJSON arity (D1 d (C1 c a)) where
991+ ) => GFromJSON' arity (C1 c a) where
824 -- The option 'tagSingleConstructors' determines whether to wrap
992 -- The option 'tagSingleConstructors' determines whether to wrap
825 -- a single-constructor type.
993 -- a single-constructor type.
826− gParseJSON opts fargs
994+ gParseJSON' p@(_ :* opts :* _)
827 | tagSingleConstructors opts
995 | tagSingleConstructors opts
828− = fmap M1
829− . (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))
830− . parseSum opts fargs
997+ . parseSum p
831− | otherwise = fmap M1 . fmap M1 . consParseJSON opts fargs
998+ | otherwise = fmap M1 . consParseJSON (cname :* p)
832−
999+ where
833−instance (ConsFromJSON arity a) => GFromJSON arity (C1 c a) where
1000+ cname = conName (undefined :: M1 _i c _f _p)
834− -- Constructors need to be decoded differently depending on whether they're
1001+
835− -- a record or not. This distinction is made by consParseJSON:
1002+-- | Multiple constructors.
836− gParseJSON opts fargs = fmap M1 . consParseJSON opts fargs
837−
838−instance ( FromProduct arity a, FromProduct arity b
839− , ProductSize a, ProductSize b
840− ) => GFromJSON arity (a :*: b) where
841− -- Products are expected to be encoded to an array. Here we check whether we
842− -- got an array of the same size as the product, then parse each of the
843− -- product's elements using parseProduct:
844− gParseJSON opts fargs = withArray "product (:*:)" $ \arr ->
845− let lenArray = V.length arr
846− lenProduct = (unTagged2 :: Tagged2 (a :*: b) Int -> Int)
847− productSize in
848− if lenArray == lenProduct
849− then parseProduct opts fargs arr 0 lenProduct
850− else fail $ "When expecting a product of " ++ show lenProduct ++
851− " values, encountered an Array of " ++ show lenArray ++
852− " elements instead"
853−
854−instance ( AllNullary (a :+: b) allNullary
1003+instance ( AllNullary (a :+: b) allNullary
855− , ParseSum arity (a :+: b) allNullary
1004+ , ParseSum arity (a :+: b) allNullary
856− ) => GFromJSON arity (a :+: b) where
1005+ ) => GFromJSON' arity (a :+: b) where
857 -- If all constructors of a sum datatype are nullary and the
1006 -- If all constructors of a sum datatype are nullary and the
858 -- 'allNullaryToStringTag' option is set they are expected to be
1007 -- 'allNullaryToStringTag' option is set they are expected to be
859 -- encoded as strings. This distinction is made by 'parseSum':
1008 -- encoded as strings. This distinction is made by 'parseSum':
860− gParseJSON opts fargs =
1009+ gParseJSON' p =
861− (unTagged :: Tagged allNullary (Parser ((a :+: b) d)) ->
1010+ (unTagged :: Tagged allNullary (Parser ((a :+: b) _d)) ->
862− Parser ((a :+: b) d))
1011+ Parser ((a :+: b) _d))
863− . parseSum opts fargs
1012+ . parseSum p
864−
865−instance (FromJSON1 f, GFromJSON One g) => GFromJSON One (f :.: g) where
866− -- If an occurrence of the last type parameter is nested inside two
867− -- composed types, it is decoded by using the outermost type's FromJSON1
868− -- instance to generically decode the innermost type:
869− gParseJSON opts fargs =
870− let gpj = gParseJSON opts fargs in
871− fmap Comp1 . liftParseJSON gpj (listParser gpj)
872
1013
873--------------------------------------------------------------------------------
1014--------------------------------------------------------------------------------
874
1015
875class ParseSum arity f allNullary where
1016class ParseSum arity f allNullary where
876− parseSum :: Options -> FromArgs arity a
1017+ parseSum :: TypeName :* Options :* FromArgs arity a
1018+ -> Value
877− -> Value -> Tagged allNullary (Parser (f a))
1019+ -> Tagged allNullary (Parser (f a))
878
1020
1021+instance ( ConstructorNames f
879−instance ( SumFromString f
1022+ , SumFromString f
880 , FromPair arity f
1023 , FromPair arity f
881 , FromTaggedObject arity f
1024 , FromTaggedObject arity f
882 , FromUntaggedValue arity f
1025 , FromUntaggedValue arity f
883 ) => ParseSum arity f True where
1026 ) => ParseSum arity f True where
884− parseSum opts fargs
1027+ parseSum p@(tname :* opts :* _)
885− | allNullaryToStringTag opts = Tagged . parseAllNullarySum opts
1028+ | allNullaryToStringTag opts = Tagged . parseAllNullarySum tname opts
886− | otherwise = Tagged . parseNonAllNullarySum opts fargs
1029+ | otherwise = Tagged . parseNonAllNullarySum p
887
1030
1031+instance ( ConstructorNames f
888−instance ( FromPair arity f
1032+ , FromPair arity f
889 , FromTaggedObject arity f
1033 , FromTaggedObject arity f
890 , FromUntaggedValue arity f
1034 , FromUntaggedValue arity f
891 ) => ParseSum arity f False where
1035 ) => ParseSum arity f False where
892− parseSum opts fargs = Tagged . parseNonAllNullarySum opts fargs
1036+ parseSum p = Tagged . parseNonAllNullarySum p
893
1037
894--------------------------------------------------------------------------------
1038--------------------------------------------------------------------------------
895
1039
896−parseAllNullarySum :: SumFromString f => Options -> Value -> Parser (f a)
1040+parseAllNullarySum :: (SumFromString f, ConstructorNames f)
1041+ => TypeName -> Options -> Value -> Parser (f a)
897−parseAllNullarySum opts = withText "Text" $ \key ->
1042+parseAllNullarySum tname opts =
1043+ withText tname $ \tag ->
898− maybe (notFound key) return $
1044+ maybe (badTag tag) return $
899− 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])
900
1062
901class SumFromString f where
1063class SumFromString f where
902− parseSumFromString :: Options -> Text -> Maybe (f a)
1064+ parseSumFromString :: (String -> String) -> Text -> Maybe (f a)
903
1065
904instance (SumFromString a, SumFromString b) => SumFromString (a :+: b) where
1066instance (SumFromString a, SumFromString b) => SumFromString (a :+: b) where
905 parseSumFromString opts key = (L1 <$> parseSumFromString opts key) <|>
1067 parseSumFromString opts key = (L1 <$> parseSumFromString opts key) <|>
906 (R1 <$> parseSumFromString opts key)
1068 (R1 <$> parseSumFromString opts key)
907
1069
908instance (Constructor c) => SumFromString (C1 c U1) where
1070instance (Constructor c) => SumFromString (C1 c U1) where
909− parseSumFromString opts key | key == name = Just $ M1 U1
1071+ parseSumFromString modifier key
1072+ | key == name = Just $ M1 U1
910− | otherwise = Nothing
1073+ | otherwise = Nothing
911− where
1074+ where
912− name = pack $ constructorTagModifier opts $
913− conName (undefined :: t c U1 p)
1075+ name = pack $ modifier $ conName (undefined :: M1 _i c _f _p)
914
1076
915−--------------------------------------------------------------------------------
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)
916
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
1111+
1112+--------------------------------------------------------------------------------
917−parseNonAllNullarySum :: ( FromPair arity f
1113+parseNonAllNullarySum :: forall f c arity.
1114+ ( FromPair arity f
918 , FromTaggedObject arity f
1115 , FromTaggedObject arity f
919 , FromUntaggedValue arity f
1116 , FromUntaggedValue arity f
1117+ , ConstructorNames f
920− ) => Options -> FromArgs arity c
1118+ ) => TypeName :* Options :* FromArgs arity c
921 -> Value -> Parser (f c)
1119 -> Value -> Parser (f c)
922−parseNonAllNullarySum opts fargs =
1120+parseNonAllNullarySum p@(tname :* opts :* _) =
923 case sumEncoding opts of
1121 case sumEncoding opts of
924 TaggedObject{..} ->
1122 TaggedObject{..} ->
925− withObject "Object" $ \obj -> do
1123+ withObject tname $ \obj -> do
926− tag <- obj .: pack tagFieldName
1124+ tag <- contextType tname . contextTag tagKey cnames_ $ obj .: tagKey
927− fromMaybe (notFound tag) $
1125+ fromMaybe (badTag tag <?> Key tagKey) $
928− 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])
929
1133
930 ObjectWithSingleField ->
1134 ObjectWithSingleField ->
931− withObject "Object" $ \obj ->
932− case H.toList obj of
1135+ withObject tname $ \obj -> case H.toList obj of
933− [pair@(tag, _)] -> fromMaybe (notFound tag) $
1136+ [(tag, v)] -> maybe (badTag tag) (<?> Key tag) $
934− parsePair opts fargs pair
1137+ parsePair (tag :* p) v
935− _ -> 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
936
1145
937 TwoElemArray ->
1146 TwoElemArray ->
938− withArray "Array" $ \arr ->
1147+ withArray tname $ \arr -> case V.length arr of
939− if V.length arr == 2
1148+ 2 | String tag <- V.unsafeIndex arr 0 ->
940− then case V.unsafeIndex arr 0 of
1149+ maybe (badTag tag <?> Index 0) (<?> Index 1) $
941− String tag -> fromMaybe (notFound tag) $
942− parsePair opts fargs (tag, V.unsafeIndex arr 1)
1150+ parsePair (tag :* p) (V.unsafeIndex arr 1)
1151+ | otherwise ->
1152+ contextType tname $
943− _ -> fail "First element is not a String"
1153+ fail "tag element is not a String" <?> Index 0
944− 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
945
1161
946− UntaggedValue -> parseUntaggedValue opts fargs
1162+ UntaggedValue -> parseUntaggedValue p
1163+ where
1164+ failWith_ = failWithCTags tname (constructorTagModifier opts)
947
1165
948--------------------------------------------------------------------------------
1166--------------------------------------------------------------------------------
949
1167
950class 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.
951− parseFromTaggedObject :: Options -> FromArgs arity a
1171+ parseFromTaggedObject
1172+ :: Text :* String :* TypeName :* Options :* FromArgs arity a
952− -> String -> Object
1173+ -> Object
953− -> Text -> Maybe (Parser (f a))
1174+ -> Maybe (Parser (f a))
954
1175
955instance ( FromTaggedObject arity a, FromTaggedObject arity b) =>
1176instance ( FromTaggedObject arity a, FromTaggedObject arity b) =>
956 FromTaggedObject arity (a :+: b) where
1177 FromTaggedObject arity (a :+: b) where
957− parseFromTaggedObject opts fargs contentsFieldName obj tag =
1178+ parseFromTaggedObject p obj =
958− (fmap L1 <$> parseFromTaggedObject opts fargs contentsFieldName obj tag) <|>
1179+ (fmap L1 <$> parseFromTaggedObject p obj) <|>
959− (fmap R1 <$> parseFromTaggedObject opts fargs contentsFieldName obj tag)
1180+ (fmap R1 <$> parseFromTaggedObject p obj)
960
1181
1182+instance ( IsRecord f isRecord
961−instance ( FromTaggedObject' arity f
1183+ , FromTaggedObject' arity f isRecord
962 , Constructor c
1184 , Constructor c
963 ) => FromTaggedObject arity (C1 c f) where
1185 ) => FromTaggedObject arity (C1 c f) where
964− 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)) .
965− | tag == name = Just $ M1 <$> parseFromTaggedObject'
1190+ parseFromTaggedObject' (contentsFieldName :* cname :* p)
966− opts fargs contentsFieldName obj
967− | otherwise = Nothing
1191+ | otherwise = const Nothing
968− where
1192+ where
969− name = pack $ constructorTagModifier opts $
1193+ tag' = pack $ constructorTagModifier opts cname
970− conName (undefined :: t c f p)
1194+ cname = conName (undefined :: M1 _i c _f _p)
971
1195
972--------------------------------------------------------------------------------
1196--------------------------------------------------------------------------------
973
1197
974−class FromTaggedObject' arity f where
1198+class FromTaggedObject' arity f isRecord where
975− parseFromTaggedObject' :: Options -> FromArgs arity a -> String
1199+ -- The first component of the parameter tuple is the contents field name.
976− -> Object -> Parser (f a)
1200+ parseFromTaggedObject'
977−
978−class FromTaggedObject'' arity f isRecord where
979− parseFromTaggedObject'' :: Options -> FromArgs arity a -> String
1201+ :: String :* ConName :* TypeName :* Options :* FromArgs arity a
980− -> Object -> Tagged isRecord (Parser (f a))
1202+ -> Object -> Tagged isRecord (Parser (f a))
981−
1203+
982−instance ( IsRecord f isRecord
983− , FromTaggedObject'' arity f isRecord
984− ) => FromTaggedObject' arity f where
1204+instance (RecordFromJSON arity f, FieldNames f) => FromTaggedObject' arity f True where
985− parseFromTaggedObject' opts fargs contentsFieldName =
1205+ -- Records are unpacked in the tagged object
986− (unTagged :: Tagged isRecord (Parser (f a)) -> Parser (f a)) .
987− parseFromTaggedObject'' opts fargs contentsFieldName
1206+ parseFromTaggedObject' (_ :* p) = Tagged . recordParseJSON (True :* p)
988−
1207+
989−instance (FromRecord arity f) => FromTaggedObject'' arity f True where
990− parseFromTaggedObject'' opts fargs _ =
991− Tagged . parseRecord opts fargs
992−
993−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
994− 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
995− (gParseJSON opts fargs <=< (.: pack contentsFieldName))
1214+ key = pack contentsFieldName
1215+ contentsFieldName :* p'@(cname :* tname :* _) = p
996
1216
997−instance OVERLAPPING_ FromTaggedObject'' arity U1 False where
1217+instance OVERLAPPING_ FromTaggedObject' arity U1 False where
1218+ -- Nullary constructors don't need a contents field
998− parseFromTaggedObject'' _ _ _ _ = Tagged (pure U1)
1219+ parseFromTaggedObject' _ _ = Tagged (pure U1)
999
1220
1000--------------------------------------------------------------------------------
1221--------------------------------------------------------------------------------
1001
1222
1223+-- | Constructors need to be decoded differently depending on whether they're
1224+-- a record or not. This distinction is made by 'ConsParseJSON'.
1002class ConsFromJSON arity f where
1225class ConsFromJSON arity f where
1226+ consParseJSON
1003− consParseJSON :: Options -> FromArgs arity a
1227+ :: ConName :* TypeName :* Options :* FromArgs arity a
1004− -> Value -> Parser (f a)
1228+ -> Value -> Parser (f a)
1005
1229
1006class ConsFromJSON' arity f isRecord where
1230class ConsFromJSON' arity f isRecord where
1231+ consParseJSON'
1007− consParseJSON' :: Options -> FromArgs arity a
1232+ :: ConName :* TypeName :* Options :* FromArgs arity a
1008− -> Value -> Tagged isRecord (Parser (f a))
1233+ -> Value -> Tagged isRecord (Parser (f a))
1009
1234
1010instance ( IsRecord f isRecord
1235instance ( IsRecord f isRecord
1011 , ConsFromJSON' arity f isRecord
1236 , ConsFromJSON' arity f isRecord
1012 ) => ConsFromJSON arity f where
1237 ) => ConsFromJSON arity f where
1013− consParseJSON opts fargs =
1238+ consParseJSON p =
1014 (unTagged :: Tagged isRecord (Parser (f a)) -> Parser (f a))
1239 (unTagged :: Tagged isRecord (Parser (f a)) -> Parser (f a))
1015− . consParseJSON' opts fargs
1240+ . consParseJSON' p
1016
1241
1017instance OVERLAPPING_
1242instance OVERLAPPING_
1018− ( GFromJSON arity a, FromRecord arity (S1 s a)
1243+ ( GFromJSON arity a, RecordFromJSON arity (S1 s a)
1019 ) => ConsFromJSON' arity (S1 s a) True where
1244 ) => ConsFromJSON' arity (S1 s a) True where
1020− consParseJSON' opts fargs
1245+ consParseJSON' p@(cname :* tname :* opts :* fargs)
1021− | unwrapUnaryRecords opts = Tagged . gParseJSON opts fargs
1246+ | unwrapUnaryRecords opts = Tagged . fmap M1 . gParseJSON opts fargs
1022− | otherwise = Tagged . withObject "unary record" (parseRecord opts fargs)
1247+ | otherwise = Tagged . withObject (showCons cname tname) (recordParseJSON (False :* p))
1023
1248
1024−instance FromRecord arity f => ConsFromJSON' arity f True where
1249+instance RecordFromJSON arity f => ConsFromJSON' arity f True where
1025− consParseJSON' opts fargs =
1250+ consParseJSON' p@(cname :* tname :* _) =
1026− Tagged . withObject "record (:*:)" (parseRecord opts fargs)
1251+ Tagged . withObject (showCons cname tname) (recordParseJSON (False :* p))
1027
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_
1028−instance GFromJSON arity f => ConsFromJSON' arity f False where
1267+ GFromJSON arity f => ConsFromJSON' arity (S1 s f) False where
1268+ consParseJSON' (_ :* _ :* opts :* fargs) =
1029− 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
1030
1274
1031--------------------------------------------------------------------------------
1275--------------------------------------------------------------------------------
1032
1276
1033−class FromRecord arity f where
1277+class FieldNames f where
1034− parseRecord :: Options -> FromArgs arity a
1278+ fieldNames :: f a -> [Text] -> [Text]
1035− -> Object -> Parser (f a)
1036
1279
1037−instance ( FromRecord arity a
1280+instance (FieldNames a, FieldNames b) => FieldNames (a :*: b) where
1038− , FromRecord arity b
1281+ fieldNames _ =
1039− ) => FromRecord arity (a :*: b) where
1282+ fieldNames (undefined :: a x) .
1040− parseRecord opts fargs obj =
1283+ fieldNames (undefined :: b y)
1041− (:*:) <$> parseRecord opts fargs obj
1042− <*> parseRecord opts fargs obj
1043
1284
1044−instance OVERLAPPABLE_ (Selector s, GFromJSON arity a) =>
1285+instance (Selector s) => FieldNames (S1 s f) where
1045− FromRecord arity (S1 s a) where
1286+ fieldNames _ = (pack (selName (undefined :: M1 _i s _f _p)) :)
1046− parseRecord opts fargs =
1287+
1047− (<?> 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
1048 where
1298 where
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
1049− label = pack . fieldLabelModifier opts $ selName (undefined :: t s a p)
1329+ label = pack $ fieldLabelModifier opts sname
1330+ sname = selName (undefined :: M1 _i s _f _p)
1050
1331
1051instance INCOHERENT_ (Selector s, FromJSON a) =>
1332instance INCOHERENT_ (Selector s, FromJSON a) =>
1052− FromRecord arity (S1 s (K1 i (Maybe a))) where
1333+ RecordFromJSON' arity (S1 s (K1 i (Maybe a))) where
1053− parseRecord opts _ obj = M1 . K1 <$> obj .:? pack label
1334+ recordParseJSON' (_ :* _ :* opts :* _) obj = M1 . K1 <$> obj .:? pack label
1054− where
1335+ where
1055− label = fieldLabelModifier opts $
1336+ label = fieldLabelModifier opts sname
1056− selName (undefined :: t s (K1 i (Maybe a)) p)
1337+ sname = selName (undefined :: M1 _i s _f _p)
1057
1338
1058-- Parse an Option like a Maybe.
1339-- Parse an Option like a Maybe.
1059instance INCOHERENT_ (Selector s, FromJSON a) =>
1340instance INCOHERENT_ (Selector s, FromJSON a) =>
1060− FromRecord arity (S1 s (K1 i (Semigroup.Option a))) where
1341+ RecordFromJSON' arity (S1 s (K1 i (Semigroup.Option a))) where
1061− parseRecord opts fargs obj = wrap <$> parseRecord opts fargs obj
1342+ recordParseJSON' p obj = wrap <$> recordParseJSON' p obj
1062 where
1343 where
1063 wrap :: S1 s (K1 i (Maybe a)) p -> S1 s (K1 i (Semigroup.Option a)) p
1344 wrap :: S1 s (K1 i (Maybe a)) p -> S1 s (K1 i (Semigroup.Option a)) p
1064 wrap (M1 (K1 a)) = M1 (K1 (Semigroup.Option a))
1345 wrap (M1 (K1 a)) = M1 (K1 (Semigroup.Option a))
1065
1346
1066--------------------------------------------------------------------------------
1347--------------------------------------------------------------------------------
1067
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+
1068−class FromProduct arity f where
1369+class ProductFromJSON arity f where
1069− parseProduct :: Options -> FromArgs arity a
1370+ productParseJSON :: ConName :* TypeName :* Options :* FromArgs arity a
1070 -> Array -> Int -> Int
1371 -> Array -> Int -> Int
1071 -> Parser (f a)
1372 -> Parser (f a)
1072
1373
1073−instance ( FromProduct arity a
1374+instance ( ProductFromJSON arity a
1074− , FromProduct arity b
1375+ , ProductFromJSON arity b
1075− ) => FromProduct arity (a :*: b) where
1376+ ) => ProductFromJSON arity (a :*: b) where
1076− parseProduct opts fargs arr ix len =
1377+ productParseJSON p arr ix len =
1077− (:*:) <$> parseProduct opts fargs arr ix lenL
1378+ (:*:) <$> productParseJSON p arr ix lenL
1078− <*> parseProduct opts fargs arr ixR lenR
1379+ <*> productParseJSON p arr ixR lenR
1079 where
1380 where
1080 lenL = len `unsafeShiftR` 1
1381 lenL = len `unsafeShiftR` 1
1081 ixR = ix + lenL
1382 ixR = ix + lenL
1082 lenR = len - lenL
1383 lenR = len - lenL
1083
1384
1084−instance (GFromJSON arity a) => FromProduct arity (S1 s a) where
1385+instance (GFromJSON arity a) => ProductFromJSON arity (S1 s a) where
1085− parseProduct opts fargs arr ix _ =
1386+ productParseJSON (_ :* _ :* opts :* fargs) arr ix _ =
1086− gParseJSON opts fargs $ V.unsafeIndex arr ix
1387+ M1 <$> gParseJSON opts fargs (V.unsafeIndex arr ix) <?> Index ix
1087
1388
1088--------------------------------------------------------------------------------
1389--------------------------------------------------------------------------------
1089
1390
1090class FromPair arity f where
1391class FromPair arity f where
1392+ -- The first component of the parameter tuple is the tag to match.
1091− parsePair :: Options -> FromArgs arity a
1393+ parsePair :: Text :* TypeName :* Options :* FromArgs arity a
1394+ -> Value
1092− -> Pair -> Maybe (Parser (f a))
1395+ -> Maybe (Parser (f a))
1093
1396
1094instance ( FromPair arity a
1397instance ( FromPair arity a
1095 , FromPair arity b
1398 , FromPair arity b
1096 ) => FromPair arity (a :+: b) where
1399 ) => FromPair arity (a :+: b) where
1400+ parsePair p pair =
1097− parsePair opts fargs pair = (fmap L1 <$> parsePair opts fargs pair) <|>
1401+ (fmap L1 <$> parsePair p pair) <|>
1098− (fmap R1 <$> parsePair opts fargs pair)
1402+ (fmap R1 <$> parsePair p pair)
1099
1403
1100instance ( Constructor c
1404instance ( Constructor c
1101− , GFromJSON arity a
1102 , ConsFromJSON arity a
1405 , ConsFromJSON arity a
1103 ) => FromPair arity (C1 c a) where
1406 ) => FromPair arity (C1 c a) where
1104− parsePair opts fargs (tag, value)
1407+ parsePair (tag :* p@(_ :* opts :* _)) v
1105− | tag == tag' = Just $ gParseJSON opts fargs value
1408+ | tag == tag' = Just $ M1 <$> consParseJSON (cname :* p) v
1106 | otherwise = Nothing
1409 | otherwise = Nothing
1107− where
1410+ where
1108− tag' = pack $ constructorTagModifier opts $
1411+ tag' = pack $ constructorTagModifier opts cname
1109− conName (undefined :: t c a p)
1412+ cname = conName (undefined :: M1 _i c _a _p)
1110
1413
1111--------------------------------------------------------------------------------
1414--------------------------------------------------------------------------------
1112
1415
1113class FromUntaggedValue arity f where
1416class FromUntaggedValue arity f where
1114− parseUntaggedValue :: Options -> FromArgs arity a
1417+ parseUntaggedValue :: TypeName :* Options :* FromArgs arity a
1418+ -> Value
1115− -> Value -> Parser (f a)
1419+ -> Parser (f a)
1116
1420
1117instance
1421instance
1118 ( FromUntaggedValue arity a
1422 ( FromUntaggedValue arity a
1119 , FromUntaggedValue arity b
1423 , FromUntaggedValue arity b
1120 ) => FromUntaggedValue arity (a :+: b)
1424 ) => FromUntaggedValue arity (a :+: b)
1121 where
1425 where
1122− parseUntaggedValue opts fargs value =
1426+ parseUntaggedValue p value =
1123− L1 <$> parseUntaggedValue opts fargs value <|>
1427+ L1 <$> parseUntaggedValue p value <|>
1124− R1 <$> parseUntaggedValue opts fargs value
1428+ R1 <$> parseUntaggedValue p value
1125
1429
1126instance OVERLAPPABLE_
1430instance OVERLAPPABLE_
1127− ( GFromJSON arity a
1431+ ( ConsFromJSON arity a
1128− , ConsFromJSON arity a
1432+ , Constructor c
1129 ) => FromUntaggedValue arity (C1 c a)
1433 ) => FromUntaggedValue arity (C1 c a)
1130 where
1434 where
1131− parseUntaggedValue = gParseJSON
1435+ parseUntaggedValue p = fmap M1 . consParseJSON (cname :* p)
1436+ where
1437+ cname = conName (undefined :: M1 _i c _f _p)
1132
1438
1133instance OVERLAPPING_
1439instance OVERLAPPING_
1134 ( Constructor c )
1440 ( Constructor c )
1135 => FromUntaggedValue arity (C1 c U1)
1441 => FromUntaggedValue arity (C1 c U1)
1136 where
1442 where
1137− parseUntaggedValue opts _ (String s)
1443+ parseUntaggedValue (tname :* opts :* _) v =
1138− | s == pack (constructorTagModifier opts (conName (undefined :: t c U1 p))) =
1444+ contextCons cname tname $ case v of
1445+ String tag
1139− pure $ M1 U1
1446+ | tag == tag' -> pure $ M1 U1
1140− | otherwise =
1447+ | otherwise -> fail_ tag
1141− fail $ "Invalid tag: " ++ unpack s
1448+ _ -> typeMismatch "String" v
1142− parseUntaggedValue _ _ v = typeMismatch (conName (undefined :: t c U1 p)) v
1449+ where
1143−
1450+ tag' = pack $ constructorTagModifier opts cname
1144−
1451+ cname = conName (undefined :: M1 _i c _f _p)
1145−--------------------------------------------------------------------------------
1452+ fail_ tag = fail $
1146−
1453+ "expected tag " ++ show tag' ++ ", but found tag " ++ show tag
1147−notFound :: Text -> Parser a
1148−notFound key = fail $ "The key \"" ++ unpack key ++ "\" was not found"
1149−{-# INLINE notFound #-}
1150
1454
1151-------------------------------------------------------------------------------
1455-------------------------------------------------------------------------------
1152-- Instances
1456-- Instances
@@ -1209,14 +1513,15 @@ instance FromJSON Void where
1209 {-# INLINE parseJSON #-}
1513 {-# INLINE parseJSON #-}
1210
1514
1211instance FromJSON Bool where
1515instance FromJSON Bool where
1212− parseJSON = withBool "Bool" pure
1516+ parseJSON (Bool b) = pure b
1517+ parseJSON v = typeMismatch "Bool" v
1213 {-# INLINE parseJSON #-}
1518 {-# INLINE parseJSON #-}
1214
1519
1215instance FromJSONKey Bool where
1520instance FromJSONKey Bool where
1216 fromJSONKey = FromJSONKeyTextParser $ \t -> case t of
1521 fromJSONKey = FromJSONKeyTextParser $ \t -> case t of
1217 "true" -> pure True
1522 "true" -> pure True
1218 "false" -> pure False
1523 "false" -> pure False
1219− _ -> fail $ "Cannot parse key into Bool: " ++ T.unpack t
1524+ _ -> fail $ "cannot parse key " ++ show t ++ " into Bool"
1220
1525
1221instance FromJSON Ordering where
1526instance FromJSON Ordering where
1222 parseJSON = withText "Ordering" $ \s ->
1527 parseJSON = withText "Ordering" $ \s ->
@@ -1224,25 +1529,30 @@ instance FromJSON Ordering where
1224 "LT" -> return LT
1529 "LT" -> return LT
1225 "EQ" -> return EQ
1530 "EQ" -> return EQ
1226 "GT" -> return GT
1531 "GT" -> return GT
1532+ _ -> fail $ "parsing Ordering failed, unexpected " ++ show s ++
1227− _ -> fail "Parsing Ordering value failed: expected \"LT\", \"EQ\", or \"GT\""
1533+ " (expected \"LT\", \"EQ\", or \"GT\")"
1228
1534
1229instance FromJSON () where
1535instance FromJSON () where
1230 parseJSON = withArray "()" $ \v ->
1536 parseJSON = withArray "()" $ \v ->
1231 if V.null v
1537 if V.null v
1232 then pure ()
1538 then pure ()
1233− else fail "Expected an empty array"
1539+ else prependContext "()" $ fail "expected an empty array"
1234 {-# INLINE parseJSON #-}
1540 {-# INLINE parseJSON #-}
1235
1541
1236instance FromJSON Char where
1542instance FromJSON Char where
1237− parseJSON = withText "Char" $ \t ->
1543+ parseJSON = withText "Char" parseChar
1238− if T.compareLength t 1 == EQ
1239− then pure $ T.head t
1240− else fail "Expected a string of length 1"
1241 {-# INLINE parseJSON #-}
1544 {-# INLINE parseJSON #-}
1242
1545
1243− parseJSONList = withText "String" $ pure . T.unpack
1546+ parseJSONList (String s) = pure (T.unpack s)
1547+ parseJSONList v = typeMismatch "String" v
1244 {-# INLINE parseJSONList #-}
1548 {-# INLINE parseJSONList #-}
1245
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+
1246instance FromJSON Double where
1556instance FromJSON Double where
1247 parseJSON = parseRealFloat "Double"
1557 parseJSON = parseRealFloat "Double"
1248 {-# INLINE parseJSON #-}
1558 {-# INLINE parseJSON #-}
@@ -1266,12 +1576,22 @@ instance FromJSONKey Float where
1266 _ -> Scientific.toRealFloat <$> parseScientificText t
1576 _ -> Scientific.toRealFloat <$> parseScientificText t
1267
1577
1268instance (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
1269− parseJSON = withObject "Rational" $ \obj -> do
1589+ objParser = withObject "Rational" $ \obj -> do
1270− numerator <- obj .: "numerator"
1590+ numerator <- obj .: "numerator"
1271− denominator <- obj .: "denominator"
1591+ denominator <- obj .: "denominator"
1272− if denominator == 0
1592+ if denominator == 0
1273− then fail "Ratio denominator was 0"
1593+ then fail "Ratio denominator was 0"
1274− else pure $ numerator % denominator
1594+ else pure $ numerator % denominator
1275 {-# INLINE parseJSON #-}
1595 {-# INLINE parseJSON #-}
1276
1596
1277-- | This instance includes a bounds check to prevent maliciously
1597-- | This instance includes a bounds check to prevent maliciously
@@ -1279,7 +1599,7 @@ instance (FromJSON a, Integral a) => FromJSON (Ratio a) where
1279-- newtype 'Scientific' and provide your own instance using
1599-- newtype 'Scientific' and provide your own instance using
1280-- 'withScientific' if you want to allow larger inputs.
1600-- 'withScientific' if you want to allow larger inputs.
1281instance HasResolution a => FromJSON (Fixed a) where
1601instance HasResolution a => FromJSON (Fixed a) where
1282− parseJSON = withBoundedScientific "Fixed" $ pure . realToFrac
1602+ parseJSON = prependContext "Fixed" . withBoundedScientific' (pure . realToFrac)
1283 {-# INLINE parseJSON #-}
1603 {-# INLINE parseJSON #-}
1284
1604
1285instance FromJSON Int where
1605instance FromJSON Int where
@@ -1302,19 +1622,20 @@ instance FromJSONKey Integer where
1302
1622
1303instance FromJSON Natural where
1623instance FromJSON Natural where
1304 parseJSON value = do
1624 parseJSON value = do
1305− integer :: Integer <- parseIntegral "Natural" value
1625+ integer <- parseIntegral "Natural" value
1306− if integer < 0 then
1626+ parseNatural integer
1307− fail $ "expected Natural, encountered negative number " <> show integer
1308− else
1309− pure $ fromIntegral integer
1310
1627
1311instance FromJSONKey Natural where
1628instance FromJSONKey Natural where
1312 fromJSONKey = FromJSONKeyTextParser $ \text -> do
1629 fromJSONKey = FromJSONKeyTextParser $ \text -> do
1313− integer :: Integer <- parseIntegralText "Natural" text
1630+ integer <- parseIntegralText "Natural" text
1631+ parseNatural integer
1632+
1633+parseNatural :: Integer -> Parser Natural
1634+parseNatural integer =
1314− if integer < 0 then
1635+ if integer < 0 then
1315− fail $ "expected Natural, encountered negative number " <> show integer
1636+ fail $ "parsing Natural failed, unexpected negative number " <> show integer
1316− else
1637+ else
1317− pure $ fromIntegral integer
1638+ pure $ fromIntegral integer
1318
1639
1319instance FromJSON Int8 where
1640instance FromJSON Int8 where
1320 parseJSON = parseBoundedIntegral "Int8"
1641 parseJSON = parseBoundedIntegral "Int8"
@@ -1411,17 +1732,17 @@ parseVersionText = go . readP_to_S parseVersion . unpack
1411 where
1732 where
1412 go [(v,[])] = return v
1733 go [(v,[])] = return v
1413 go (_ : xs) = go xs
1734 go (_ : xs) = go xs
1414− go _ = fail "could not parse Version"
1735+ go _ = fail "parsing Version failed"
1415
1736
1416-------------------------------------------------------------------------------
1737-------------------------------------------------------------------------------
1417-- semigroups NonEmpty
1738-- semigroups NonEmpty
1418-------------------------------------------------------------------------------
1739-------------------------------------------------------------------------------
1419
1740
1420instance FromJSON1 NonEmpty where
1741instance FromJSON1 NonEmpty where
1421− liftParseJSON p _ = withArray "NonEmpty a" $
1742+ liftParseJSON p _ = withArray "NonEmpty" $
1422 (>>= ne) . Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1743 (>>= ne) . Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1423 where
1744 where
1424− ne [] = fail "Expected a NonEmpty but got an empty list"
1745+ ne [] = fail "parsing NonEmpty failed, unexpected empty list"
1425 ne (x:xs) = pure (x :| xs)
1746 ne (x:xs) = pure (x :| xs)
1426 {-# INLINE liftParseJSON #-}
1747 {-# INLINE liftParseJSON #-}
1427
1748
@@ -1442,7 +1763,7 @@ instance FromJSON Scientific where
1442-------------------------------------------------------------------------------
1763-------------------------------------------------------------------------------
1443
1764
1444instance FromJSON1 DList.DList where
1765instance FromJSON1 DList.DList where
1445− liftParseJSON p _ = withArray "DList a" $
1766+ liftParseJSON p _ = withArray "DList" $
1446 fmap DList.fromList .
1767 fmap DList.fromList .
1447 Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1768 Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1448 {-# INLINE liftParseJSON #-}
1769 {-# INLINE liftParseJSON #-}
@@ -1519,7 +1840,7 @@ instance (FromJSON1 f, FromJSON1 g) => FromJSON1 (Sum f g) where
1519 inr = "InR"
1840 inr = "InR"
1520
1841
1521 liftParseJSON _ _ _ = fail $
1842 liftParseJSON _ _ _ = fail $
1522− "expected an object with a single property " ++
1843+ "parsing Sum failed, expected an object with a single property " ++
1523 "where the property key should be either " ++
1844 "where the property key should be either " ++
1524 "\"InL\" or \"InR\""
1845 "\"InL\" or \"InR\""
1525 {-# INLINE liftParseJSON #-}
1846 {-# INLINE liftParseJSON #-}
@@ -1533,7 +1854,7 @@ instance (FromJSON1 f, FromJSON1 g, FromJSON a) => FromJSON (Sum f g a) where
1533-------------------------------------------------------------------------------
1854-------------------------------------------------------------------------------
1534
1855
1535instance FromJSON1 Seq.Seq where
1856instance FromJSON1 Seq.Seq where
1536− liftParseJSON p _ = withArray "Seq a" $
1857+ liftParseJSON p _ = withArray "Seq" $
1537 fmap Seq.fromList .
1858 fmap Seq.fromList .
1538 Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1859 Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1539 {-# INLINE liftParseJSON #-}
1860 {-# INLINE liftParseJSON #-}
@@ -1567,13 +1888,13 @@ instance FromJSON a => FromJSON (IntMap.IntMap a) where
1567
1888
1568instance (FromJSONKey k, Ord k) => FromJSON1 (M.Map k) where
1889instance (FromJSONKey k, Ord k) => FromJSON1 (M.Map k) where
1569 liftParseJSON p _ = case fromJSONKey of
1890 liftParseJSON p _ = case fromJSONKey of
1570− FromJSONKeyCoerce _-> withObject "Map k v" $
1891+ FromJSONKeyCoerce _ -> withObject "Map" $
1571 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)
1572− FromJSONKeyText f -> withObject "Map k v" $
1893+ FromJSONKeyText f -> withObject "Map" $
1573 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)
1574− FromJSONKeyTextParser f -> withObject "Map k v" $
1895+ FromJSONKeyTextParser f -> withObject "Map" $
1575 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)
1576− FromJSONKeyValue f -> withArray "Map k v" $ \arr ->
1897+ FromJSONKeyValue f -> withArray "Map" $ \arr ->
1577 fmap M.fromList . Tr.sequence .
1898 fmap M.fromList . Tr.sequence .
1578 zipWith (parseIndexedJSONPair f p) [0..] . V.toList $ arr
1899 zipWith (parseIndexedJSONPair f p) [0..] . V.toList $ arr
1579 {-# INLINE liftParseJSON #-}
1900 {-# INLINE liftParseJSON #-}
@@ -1601,18 +1922,18 @@ instance (FromJSON v) => FromJSON (Tree.Tree v) where
1601
1922
1602instance FromJSON UUID.UUID where
1923instance FromJSON UUID.UUID where
1603 parseJSON = withText "UUID" $
1924 parseJSON = withText "UUID" $
1604− maybe (fail "Invalid UUID") pure . UUID.fromText
1925+ maybe (fail "invalid UUID") pure . UUID.fromText
1605
1926
1606instance FromJSONKey UUID.UUID where
1927instance FromJSONKey UUID.UUID where
1607 fromJSONKey = FromJSONKeyTextParser $
1928 fromJSONKey = FromJSONKeyTextParser $
1608− maybe (fail "Invalid UUID") pure . UUID.fromText
1929+ maybe (fail "invalid UUID") pure . UUID.fromText
1609
1930
1610-------------------------------------------------------------------------------
1931-------------------------------------------------------------------------------
1611-- vector
1932-- vector
1612-------------------------------------------------------------------------------
1933-------------------------------------------------------------------------------
1613
1934
1614instance FromJSON1 Vector where
1935instance FromJSON1 Vector where
1615− liftParseJSON p _ = withArray "Vector a" $
1936+ liftParseJSON p _ = withArray "Vector" $
1616 V.mapM (uncurry $ parseIndexedJSON p) . V.indexed
1937 V.mapM (uncurry $ parseIndexedJSON p) . V.indexed
1617 {-# INLINE liftParseJSON #-}
1938 {-# INLINE liftParseJSON #-}
1618
1939
@@ -1626,14 +1947,14 @@ vectorParseJSON s = withArray s $ fmap V.convert . V.mapM (uncurry $ parseIndexe
1626{-# INLINE vectorParseJSON #-}
1947{-# INLINE vectorParseJSON #-}
1627
1948
1628instance (Storable a, FromJSON a) => FromJSON (VS.Vector a) where
1949instance (Storable a, FromJSON a) => FromJSON (VS.Vector a) where
1629− parseJSON = vectorParseJSON "Data.Vector.Storable.Vector a"
1950+ parseJSON = vectorParseJSON "Data.Vector.Storable.Vector"
1630
1951
1631instance (VP.Prim a, FromJSON a) => FromJSON (VP.Vector a) where
1952instance (VP.Prim a, FromJSON a) => FromJSON (VP.Vector a) where
1632− parseJSON = vectorParseJSON "Data.Vector.Primitive.Vector a"
1953+ parseJSON = vectorParseJSON "Data.Vector.Primitive.Vector"
1633 {-# INLINE parseJSON #-}
1954 {-# INLINE parseJSON #-}
1634
1955
1635instance (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
1636− parseJSON = vectorParseJSON "Data.Vector.Unboxed.Vector a"
1957+ parseJSON = vectorParseJSON "Data.Vector.Unboxed.Vector"
1637 {-# INLINE parseJSON #-}
1958 {-# INLINE parseJSON #-}
1638
1959
1639-------------------------------------------------------------------------------
1960-------------------------------------------------------------------------------
@@ -1647,13 +1968,13 @@ instance (Eq a, Hashable a, FromJSON a) => FromJSON (HashSet.HashSet a) where
1647
1968
1648instance (FromJSONKey k, Eq k, Hashable k) => FromJSON1 (H.HashMap k) where
1969instance (FromJSONKey k, Eq k, Hashable k) => FromJSON1 (H.HashMap k) where
1649 liftParseJSON p _ = case fromJSONKey of
1970 liftParseJSON p _ = case fromJSONKey of
1650− FromJSONKeyCoerce _ -> withObject "HashMap ~Text v" $
1971+ FromJSONKeyCoerce _ -> withObject "HashMap ~Text" $
1651 uc . H.traverseWithKey (\k v -> p v <?> Key k)
1972 uc . H.traverseWithKey (\k v -> p v <?> Key k)
1652− FromJSONKeyText f -> withObject "HashMap k v" $
1973+ FromJSONKeyText f -> withObject "HashMap" $
1653 fmap (mapKey f) . H.traverseWithKey (\k v -> p v <?> Key k)
1974 fmap (mapKey f) . H.traverseWithKey (\k v -> p v <?> Key k)
1654− FromJSONKeyTextParser f -> withObject "HashMap k v" $
1975+ FromJSONKeyTextParser f -> withObject "HashMap" $
1655 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)
1656− FromJSONKeyValue f -> withArray "Map k v" $ \arr ->
1977+ FromJSONKeyValue f -> withArray "Map" $ \arr ->
1657 fmap H.fromList . Tr.sequence .
1978 fmap H.fromList . Tr.sequence .
1658 zipWith (parseIndexedJSONPair f p) [0..] . V.toList $ arr
1979 zipWith (parseIndexedJSONPair f p) [0..] . V.toList $ arr
1659 where
1980 where
@@ -1676,11 +1997,33 @@ instance FromJSON DotNetTime where
1676 parseJSON = withText "DotNetTime" $ \t ->
1997 parseJSON = withText "DotNetTime" $ \t ->
1677 let (s,m) = T.splitAt (T.length t - 5) t
1998 let (s,m) = T.splitAt (T.length t - 5) t
1678 t' = T.concat [s,".",m]
1999 t' = T.concat [s,".",m]
1679− in case parseTime defaultTimeLocale "/Date(%s%Q)/" (unpack t') of
2000+ in case parseTimeM True defaultTimeLocale "/Date(%s%Q)/" (unpack t') of
1680 Just d -> pure (DotNetTime d)
2001 Just d -> pure (DotNetTime d)
1681 _ -> fail "could not parse .NET time"
2002 _ -> fail "could not parse .NET time"
1682 {-# INLINE parseJSON #-}
2003 {-# INLINE parseJSON #-}
1683
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+
1684-------------------------------------------------------------------------------
2027-------------------------------------------------------------------------------
1685-- time
2028-- time
1686-------------------------------------------------------------------------------
2029-------------------------------------------------------------------------------
@@ -1748,6 +2091,40 @@ instance FromJSON DiffTime where
1748 parseJSON = withBoundedScientific "DiffTime" $ pure . realToFrac
2091 parseJSON = withBoundedScientific "DiffTime" $ pure . realToFrac
1749 {-# INLINE parseJSON #-}
2092 {-# INLINE parseJSON #-}
1750
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
2127+
1751-------------------------------------------------------------------------------
2128-------------------------------------------------------------------------------
1752-- base Monoid/Semigroup
2129-- base Monoid/Semigroup
1753-------------------------------------------------------------------------------
2130-------------------------------------------------------------------------------
@@ -1868,14 +2245,15 @@ instance FromJSON a => FromJSON (Semigroup.Option a) where
1868
2245
1869instance FromJSON1 Proxy where
2246instance FromJSON1 Proxy where
1870 {-# INLINE liftParseJSON #-}
2247 {-# INLINE liftParseJSON #-}
1871− liftParseJSON _ _ Null = pure Proxy
2248+ liftParseJSON _ _ = fromNull "Proxy" Proxy
1872− liftParseJSON _ _ v = typeMismatch "Proxy" v
1873
2249
1874instance FromJSON (Proxy a) where
2250instance FromJSON (Proxy a) where
1875 {-# INLINE parseJSON #-}
2251 {-# INLINE parseJSON #-}
1876− parseJSON Null = pure Proxy
2252+ parseJSON = fromNull "Proxy" Proxy
1877− parseJSON v = typeMismatch "Proxy" v
1878
2253
2254+fromNull :: String -> a -> Value -> Parser a
2255+fromNull _ a Null = pure a
2256+fromNull c _ v = prependContext c (typeMismatch "Null" v)
1879
2257
1880instance FromJSON2 Tagged where
2258instance FromJSON2 Tagged where
1881 liftParseJSON2 _ _ p _ = fmap Tagged . p
2259 liftParseJSON2 _ _ p _ = fmap Tagged . p
@@ -1902,10 +2280,7 @@ instance (FromJSON a, FromJSON b, FromJSON c) => FromJSONKey (a,b,c)
1902instance (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)
1903
2281
1904instance FromJSONKey Char where
2282instance FromJSONKey Char where
1905− fromJSONKey = FromJSONKeyTextParser $ \t ->
2283+ fromJSONKey = FromJSONKeyTextParser parseChar
1906− if T.length t == 1
1907− then return (T.index t 0)
1908− else typeMismatch "Expected Char but String didn't contain exactly one character" (String t)
1909 fromJSONKeyList = FromJSONKeyText T.unpack
2284 fromJSONKeyList = FromJSONKeyText T.unpack
1910
2285
1911instance (FromJSONKey a, FromJSON a) => FromJSONKey [a] where
2286instance (FromJSONKey a, FromJSON a) => FromJSONKey [a] where
Data/Aeson/Types/Generic.hs+10−1
@@ -1,10 +1,10 @@
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 NoImplicitPrelude #-}
9{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE ScopedTypeVariables #-}
10{-# LANGUAGE TypeOperators #-}
10{-# LANGUAGE TypeOperators #-}
@@ -35,6 +35,7 @@ module Data.Aeson.Types.Generic
35 , Zero
35 , Zero
36 , One
36 , One
37 , ProductSize(..)
37 , ProductSize(..)
38+ , (:*)(..)
38 ) where
39 ) where
39
40
40import Prelude.Compat
41import Prelude.Compat
@@ -75,6 +76,7 @@ instance AllNullary (Rec1 f) False
75instance AllNullary U1 True
76instance AllNullary U1 True
76
77
77newtype Tagged2 (s :: * -> *) b = Tagged2 {unTagged2 :: b}
78newtype Tagged2 (s :: * -> *) b = Tagged2 {unTagged2 :: b}
79+ deriving Functor
78
80
79--------------------------------------------------------------------------------
81--------------------------------------------------------------------------------
80
82
@@ -107,3 +109,10 @@ instance (ProductSize a, ProductSize b) => ProductSize (a :*: b) where
107
109
108instance ProductSize (S1 s a) where
110instance ProductSize (S1 s a) where
109 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+115−5
@@ -45,9 +45,12 @@ module Data.Aeson.Types.Internal
45 , parseEither
45 , parseEither
46 , parseMaybe
46 , parseMaybe
47 , modifyFailure
47 , modifyFailure
48+ , prependFailure
48 , parserThrowError
49 , parserThrowError
49 , parserCatchError
50 , parserCatchError
50 , formatError
51 , formatError
52+ , formatPath
53+ , formatRelativePath
51 , (<?>)
54 , (<?>)
52 -- * Constructors and accessors
55 -- * Constructors and accessors
53 , object
56 , object
@@ -61,11 +64,14 @@ module Data.Aeson.Types.Internal
61 , sumEncoding
64 , sumEncoding
62 , unwrapUnaryRecords
65 , unwrapUnaryRecords
63 , tagSingleConstructors
66 , tagSingleConstructors
67+ , rejectUnknownFields
64 )
68 )
65
69
66 , SumEncoding(..)
70 , SumEncoding(..)
71+ , JSONKeyOptions(keyModifier)
67 , defaultOptions
72 , defaultOptions
68 , defaultTaggedObject
73 , defaultTaggedObject
74+ , defaultJSONKeyOptions
69
75
70 -- * Used for changing CamelCase names into something else.
76 -- * Used for changing CamelCase names into something else.
71 , camelTo
77 , camelTo
@@ -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
@@ -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
@@ -281,14 +292,17 @@ newtype Parser a = Parser {
281 -> f r
292 -> f r
282 }
293 }
283
294
284−instance Monad Parser where
295+instance Monad.Monad Parser where
285 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
286 in runParser m path kf ks'
297 in runParser m path kf ks'
287 {-# INLINE (>>=) #-}
298 {-# INLINE (>>=) #-}
288 return = pure
299 return = pure
289 {-# INLINE return #-}
300 {-# INLINE return #-}
301+
302+#if !(MIN_VERSION_base(4,13,0))
290 fail = Fail.fail
303 fail = Fail.fail
291 {-# INLINE fail #-}
304 {-# INLINE fail #-}
305+#endif
292
306
293instance Fail.MonadFail Parser where
307instance Fail.MonadFail Parser where
294 fail msg = Parser $ \path kf _ks -> kf (reverse path) msg
308 fail msg = Parser $ \path kf _ks -> kf (reverse path) msg
@@ -447,7 +461,17 @@ parseEither m v = runParser (m v) [] onError Right
447-- | Annotate an error message with a
461-- | Annotate an error message with a
448-- <http://goessner.net/articles/JsonPath/ JSONPath> error location.
462-- <http://goessner.net/articles/JsonPath/ JSONPath> error location.
449formatError :: JSONPath -> String -> String
463formatError :: JSONPath -> String -> String
450−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
451 where
475 where
452 format :: String -> JSONPath -> String
476 format :: String -> JSONPath -> String
453 format pfx [] = pfx
477 format pfx [] = pfx
@@ -513,6 +537,15 @@ modifyFailure :: (String -> String) -> Parser a -> Parser a
513modifyFailure f (Parser p) = Parser $ \path kf ks ->
537modifyFailure f (Parser p) = Parser $ \path kf ks ->
514 p path (\p' m -> kf p' (f m)) ks
538 p path (\p' m -> kf p' (f m)) ks
515
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+
516-- | Throw a parser error with an additional path.
549-- | Throw a parser error with an additional path.
517--
550--
518-- @since 1.2.1.0
551-- @since 1.2.1.0
@@ -551,6 +584,39 @@ data Options = Options
551 -- ^ If 'True' record fields with a 'Nothing' value will be
584 -- ^ If 'True' record fields with a 'Nothing' value will be
552 -- omitted from the resulting object. If 'False' the resulting
585 -- omitted from the resulting object. If 'False' the resulting
553 -- 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+ -- @
554 , sumEncoding :: SumEncoding
620 , sumEncoding :: SumEncoding
555 -- ^ Specifies how to encode constructors of a sum datatype.
621 -- ^ Specifies how to encode constructors of a sum datatype.
556 , unwrapUnaryRecords :: Bool
622 , unwrapUnaryRecords :: Bool
@@ -559,10 +625,14 @@ data Options = Options
559 , tagSingleConstructors :: Bool
625 , tagSingleConstructors :: Bool
560 -- ^ Encode types with a single constructor as sums,
626 -- ^ Encode types with a single constructor as sums,
561 -- 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.
562 }
632 }
563
633
564instance Show Options where
634instance Show Options where
565− show (Options f c a o s u t) =
635+ show (Options f c a o s u t r) =
566 "Options {"
636 "Options {"
567 ++ intercalate ", "
637 ++ intercalate ", "
568 [ "fieldLabelModifier =~ " ++ show (f "exampleField")
638 [ "fieldLabelModifier =~ " ++ show (f "exampleField")
@@ -572,6 +642,7 @@ instance Show Options where
572 , "sumEncoding = " ++ show s
642 , "sumEncoding = " ++ show s
573 , "unwrapUnaryRecords = " ++ show u
643 , "unwrapUnaryRecords = " ++ show u
574 , "tagSingleConstructors = " ++ show t
644 , "tagSingleConstructors = " ++ show t
645+ , "rejectUnknownFields = " ++ show r
575 ]
646 ]
576 ++ "}"
647 ++ "}"
577
648
@@ -615,6 +686,33 @@ data SumEncoding =
615 -- contents of the constructor.
686 -- contents of the constructor.
616 deriving (Eq, Show)
687 deriving (Eq, Show)
617
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+
618-- | Default encoding 'Options':
716-- | Default encoding 'Options':
619--
717--
620-- @
718-- @
@@ -626,6 +724,7 @@ data SumEncoding =
626-- , 'sumEncoding' = 'defaultTaggedObject'
724-- , 'sumEncoding' = 'defaultTaggedObject'
627-- , 'unwrapUnaryRecords' = False
725-- , 'unwrapUnaryRecords' = False
628-- , 'tagSingleConstructors' = False
726-- , 'tagSingleConstructors' = False
727+-- , 'rejectUnknownFields' = False
629-- }
728-- }
630-- @
729-- @
631defaultOptions :: Options
730defaultOptions :: Options
@@ -637,6 +736,7 @@ defaultOptions = Options
637 , sumEncoding = defaultTaggedObject
736 , sumEncoding = defaultTaggedObject
638 , unwrapUnaryRecords = False
737 , unwrapUnaryRecords = False
639 , tagSingleConstructors = False
738 , tagSingleConstructors = False
739+ , rejectUnknownFields = False
640 }
740 }
641
741
642-- | Default 'TaggedObject' 'SumEncoding' options:
742-- | Default 'TaggedObject' 'SumEncoding' options:
@@ -653,6 +753,16 @@ defaultTaggedObject = TaggedObject
653 , contentsFieldName = "contents"
753 , contentsFieldName = "contents"
654 }
754 }
655
755
756+-- | Default 'JSONKeyOptions':
757+--
758+-- @
759+-- defaultJSONKeyOptions = 'JSONKeyOptions'
760+-- { 'keyModifier' = 'id'
761+-- }
762+-- @
763+defaultJSONKeyOptions :: JSONKeyOptions
764+defaultJSONKeyOptions = JSONKeyOptions id
765+
656-- | Converts from CamelCase to another lower case, interspersing
766-- | Converts from CamelCase to another lower case, interspersing
657-- the character between all capital letters and their previous
767-- the character between all capital letters and their previous
658-- entries, except those capital letters that appear together,
768-- entries, except those capital letters that appear together,
Data/Aeson/Types/ToJSON.hs+142−49
@@ -5,21 +5,15 @@
5{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE FunctionalDependencies #-}
6{-# LANGUAGE FunctionalDependencies #-}
7{-# LANGUAGE GADTs #-}
7{-# LANGUAGE GADTs #-}
8−{-# LANGUAGE MultiParamTypeClasses #-}
9−{-# LANGUAGE NamedFieldPuns #-}
10{-# LANGUAGE NoImplicitPrelude #-}
8{-# LANGUAGE NoImplicitPrelude #-}
11{-# LANGUAGE OverloadedStrings #-}
9{-# LANGUAGE OverloadedStrings #-}
12{-# LANGUAGE PatternGuards #-}
10{-# LANGUAGE PatternGuards #-}
11+{-# LANGUAGE PolyKinds #-}
13{-# LANGUAGE RecordWildCards #-}
12{-# LANGUAGE RecordWildCards #-}
14{-# LANGUAGE ScopedTypeVariables #-}
13{-# LANGUAGE ScopedTypeVariables #-}
15{-# LANGUAGE TypeOperators #-}
14{-# LANGUAGE TypeOperators #-}
16−{-# LANGUAGE TypeSynonymInstances #-}
17{-# LANGUAGE UndecidableInstances #-}
15{-# LANGUAGE UndecidableInstances #-}
18
16
19−#if __GLASGOW_HASKELL__ >= 706
20−{-# LANGUAGE PolyKinds #-}
21−#endif
22−
23#include "overlapping-compat.h"
17#include "overlapping-compat.h"
24#include "incoherent-compat.h"
18#include "incoherent-compat.h"
25
19
@@ -49,6 +43,10 @@ module Data.Aeson.Types.ToJSON
49 , ToJSONKeyFunction(..)
43 , ToJSONKeyFunction(..)
50 , toJSONKeyText
44 , toJSONKeyText
51 , contramapToJSONKeyFunction
45 , contramapToJSONKeyFunction
46+
47+ , GToJSONKey()
48+ , genericToJSONKey
49+
52 -- * Object key-value pairs
50 -- * Object key-value pairs
53 , KeyValue(..)
51 , KeyValue(..)
54 , KeyValuePair(..)
52 , KeyValuePair(..)
@@ -71,9 +69,10 @@ import Data.Aeson.Types.Internal
71import Data.Attoparsec.Number (Number(..))
69import Data.Attoparsec.Number (Number(..))
72import Data.Bits (unsafeShiftR)
70import Data.Bits (unsafeShiftR)
73import Data.DList (DList)
71import Data.DList (DList)
74−import Data.Fixed (Fixed, HasResolution)
72+import Data.Fixed (Fixed, HasResolution, Nano)
75import Data.Foldable (toList)
73import Data.Foldable (toList)
76import Data.Functor.Compose (Compose(..))
74import Data.Functor.Compose (Compose(..))
75+import Data.Functor.Contravariant (Contravariant (..))
77import Data.Functor.Identity (Identity(..))
76import Data.Functor.Identity (Identity(..))
78import Data.Functor.Product (Product(..))
77import Data.Functor.Product (Product(..))
79import Data.Functor.Sum (Sum(..))
78import Data.Functor.Sum (Sum(..))
@@ -87,8 +86,10 @@ import Data.Scientific (Scientific)
87import Data.Tagged (Tagged(..))
86import Data.Tagged (Tagged(..))
88import Data.Text (Text, pack)
87import Data.Text (Text, pack)
89import 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 (..))
90−import Data.Time.Format (FormatTime, formatTime)
92+import Data.Time.Format.Compat (FormatTime, formatTime, defaultTimeLocale)
91−import Data.Time.Locale.Compat (defaultTimeLocale)
92import Data.Vector (Vector)
93import Data.Vector (Vector)
93import Data.Version (Version, showVersion)
94import Data.Version (Version, showVersion)
94import Data.Void (Void, absurd)
95import Data.Void (Void, absurd)
@@ -99,7 +100,6 @@ import GHC.Generics
99import Numeric.Natural (Natural)
100import Numeric.Natural (Natural)
100import qualified Data.Aeson.Encoding as E
101import qualified Data.Aeson.Encoding as E
101import qualified Data.Aeson.Encoding.Internal as E (InArray, comma, econcat, retagEncoding)
102import qualified Data.Aeson.Encoding.Internal as E (InArray, comma, econcat, retagEncoding)
102−import qualified Data.ByteString as S
103import qualified Data.ByteString.Lazy as L
103import qualified Data.ByteString.Lazy as L
104import qualified Data.DList as DList
104import qualified Data.DList as DList
105import qualified Data.HashMap.Strict as H
105import qualified Data.HashMap.Strict as H
@@ -128,15 +128,17 @@ import qualified Data.Vector.Unboxed as VU
128import qualified Data.Aeson.Encoding.Builder as EB
128import qualified Data.Aeson.Encoding.Builder as EB
129import qualified Data.ByteString.Builder as B
129import qualified Data.ByteString.Builder as B
130
130
131−#if !(MIN_VERSION_bytestring(0,10,0))
131+import qualified GHC.Exts as Exts
132−import Foreign.ForeignPtr (withForeignPtr)
132+import qualified Data.Primitive.Array as PM
133−import Foreign.Marshal.Utils (copyBytes)
134−import Foreign.Ptr (plusPtr)
135−import qualified Data.ByteString.Internal as S
133+import qualified Data.Primitive.SmallArray as PM
136−import qualified Data.ByteString.Lazy.Internal as L
134+import qualified Data.Primitive.Types as PM
137−#endif
138
135
139−{-# 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
140
142
141toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value
143toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value
142toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b)
144toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b)
@@ -336,6 +338,13 @@ instance KeyValue Pair where
336 name .= value = (name, toJSON value)
338 name .= value = (name, toJSON value)
337 {-# INLINE (.=) #-}
339 {-# INLINE (.=) #-}
338
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+
339-------------------------------------------------------------------------------
348-------------------------------------------------------------------------------
340-- Classes and types for map keys
349-- Classes and types for map keys
341-------------------------------------------------------------------------------
350-------------------------------------------------------------------------------
@@ -393,7 +402,7 @@ instance KeyValue Pair where
393-- newtype wrapper around 'Text'. The recommended approach is to use
402-- newtype wrapper around 'Text'. The recommended approach is to use
394-- generalized newtype deriving:
403-- generalized newtype deriving:
395--
404--
396−-- > newtype RecordId = RecordId { getRecordId :: Text}
405+-- > newtype RecordId = RecordId { getRecordId :: Text }
397-- > deriving (Eq,Ord,ToJSONKey)
406-- > deriving (Eq,Ord,ToJSONKey)
398--
407--
399-- Then we may write:
408-- Then we may write:
@@ -408,8 +417,18 @@ instance KeyValue Pair where
408--
417--
409-- 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
410-- 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
411−-- and from 'Text' that does not require any escape sequences. So, in
420+-- and from 'Text' that does not require any escape sequences. So
412−-- 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+--
413-- The 'Show' instance can be used to help write 'ToJSONKey':
432-- The 'Show' instance can be used to help write 'ToJSONKey':
414--
433--
415-- > instance ToJSONKey Color where
434-- > instance ToJSONKey Color where
@@ -484,16 +503,42 @@ toJSONKeyTextEnc e = ToJSONKeyText tot e
484 -- TODO: dropAround is also used in stringEncoding, which is unfortunate atm
503 -- TODO: dropAround is also used in stringEncoding, which is unfortunate atm
485 tot = T.dropAround (== '"')
504 tot = T.dropAround (== '"')
486 . T.decodeLatin1
505 . T.decodeLatin1
487− . lazyToStrictByteString
506+ . L.toStrict
488 . E.encodingToLazyByteString
507 . E.encodingToLazyByteString
489 . e
508 . e
490
509
510+instance Contravariant ToJSONKeyFunction where
511+ contramap = contramapToJSONKeyFunction
512+
491-- | Contravariant map, as 'ToJSONKeyFunction' is a contravariant functor.
513-- | Contravariant map, as 'ToJSONKeyFunction' is a contravariant functor.
492contramapToJSONKeyFunction :: (b -> a) -> ToJSONKeyFunction a -> ToJSONKeyFunction b
514contramapToJSONKeyFunction :: (b -> a) -> ToJSONKeyFunction a -> ToJSONKeyFunction b
493contramapToJSONKeyFunction h x = case x of
515contramapToJSONKeyFunction h x = case x of
494 ToJSONKeyText f g -> ToJSONKeyText (f . h) (g . h)
516 ToJSONKeyText f g -> ToJSONKeyText (f . h) (g . h)
495 ToJSONKeyValue f g -> ToJSONKeyValue (f . h) (g . h)
517 ToJSONKeyValue f g -> ToJSONKeyValue (f . h) (g . h)
496
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+
497-------------------------------------------------------------------------------
542-------------------------------------------------------------------------------
498-- Lifings of FromJSON and ToJSON to unary and binary type constructors
543-- Lifings of FromJSON and ToJSON to unary and binary type constructors
499-------------------------------------------------------------------------------
544-------------------------------------------------------------------------------
@@ -923,6 +968,9 @@ instance (GetConName a, GetConName b) => GetConName (a :+: b) where
923instance (Constructor c) => GetConName (C1 c a) where
968instance (Constructor c) => GetConName (C1 c a) where
924 getConName = conName
969 getConName = conName
925
970
971+-- For genericToJSONKey
972+instance GetConName a => GetConName (D1 d a) where
973+ getConName (M1 x) = getConName x
926
974
927--------------------------------------------------------------------------------
975--------------------------------------------------------------------------------
928
976
@@ -1918,6 +1966,32 @@ dotNetTime (DotNetTime t) = secs ++ formatMillis t ++ ")/"
1918formatMillis :: (FormatTime t) => t -> String
1966formatMillis :: (FormatTime t) => t -> String
1919formatMillis = take 3 . formatTime defaultTimeLocale "%q"
1967formatMillis = take 3 . formatTime defaultTimeLocale "%q"
1920
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+
1921-------------------------------------------------------------------------------
1995-------------------------------------------------------------------------------
1922-- time
1996-- time
1923-------------------------------------------------------------------------------
1997-------------------------------------------------------------------------------
@@ -1966,7 +2040,7 @@ stringEncoding :: Encoding' Text -> Value
1966stringEncoding = String
2040stringEncoding = String
1967 . T.dropAround (== '"')
2041 . T.dropAround (== '"')
1968 . T.decodeLatin1
2042 . T.decodeLatin1
1969− . lazyToStrictByteString
2043+ . L.toStrict
1970 . E.encodingToLazyByteString
2044 . E.encodingToLazyByteString
1971{-# INLINE stringEncoding #-}
2045{-# INLINE stringEncoding #-}
1972
2046
@@ -1986,6 +2060,50 @@ instance ToJSON DiffTime where
1986 toEncoding = E.scientific . realToFrac
2060 toEncoding = E.scientific . realToFrac
1987 {-# INLINE toEncoding #-}
2061 {-# INLINE toEncoding #-}
1988
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+
1989-------------------------------------------------------------------------------
2107-------------------------------------------------------------------------------
1990-- base Monoid/Semigroup
2108-- base Monoid/Semigroup
1991-------------------------------------------------------------------------------
2109-------------------------------------------------------------------------------
@@ -2729,31 +2847,6 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g,
2729 toEncoding = toEncoding2
2847 toEncoding = toEncoding2
2730 {-# INLINE toEncoding #-}
2848 {-# INLINE toEncoding #-}
2731
2849
2732−-------------------------------------------------------------------------------
2733−-- pre-bytestring-0.10 compatibility
2734−-------------------------------------------------------------------------------
2735−
2736−{-# INLINE lazyToStrictByteString #-}
2737−lazyToStrictByteString :: L.ByteString -> S.ByteString
2738−#if MIN_VERSION_bytestring(0,10,0)
2739−lazyToStrictByteString = L.toStrict
2740−#else
2741−lazyToStrictByteString = packChunks
2742−
2743−-- packChunks is taken from the blaze-builder package.
2744−
2745−-- | Pack the chunks of a lazy bytestring into a single strict bytestring.
2746−packChunks :: L.ByteString -> S.ByteString
2747−packChunks lbs =
2748− S.unsafeCreate (fromIntegral $ L.length lbs) (copyChunks lbs)
2749− where
2750− copyChunks L.Empty _pf = return ()
2751− copyChunks (L.Chunk (S.PS fpbuf o l) lbs') pf = do
2752− withForeignPtr fpbuf $ \pbuf ->
2753− copyBytes pf (pbuf `plusPtr` o) l
2754− copyChunks lbs' (pf `plusPtr` l)
2755−#endif
2756−
2757--------------------------------------------------------------------------------
2850--------------------------------------------------------------------------------
2758
2851
2759-- | Wrap a list of pairs as an object.
2852-- | Wrap a list of pairs as an object.
HLint.hsdeleted−12
@@ -1,12 +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 >=>"
11−ignore "Use lambda-case"
12−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/
aeson.cabal+59−35
@@ -1,5 +1,5 @@
1name: aeson
1name: aeson
2−version: 1.4.0.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
@@ -42,6 +42,7 @@ extra-source-files:
42 include/*.h
42 include/*.h
43 tests/JSONTestSuite/test_parsing/*.json
43 tests/JSONTestSuite/test_parsing/*.json
44 tests/JSONTestSuite/test_transform/*.json
44 tests/JSONTestSuite/test_transform/*.json
45+ tests/golden/*.expected
45 pure/Data/Aeson/Parser/*.hs
46 pure/Data/Aeson/Parser/*.hs
46
47
47flag developer
48flag developer
@@ -75,6 +76,7 @@ library
75 Data.Aeson.Text
76 Data.Aeson.Text
76 Data.Aeson.Types
77 Data.Aeson.Types
77 Data.Aeson.TH
78 Data.Aeson.TH
79+ Data.Aeson.QQ.Simple
78
80
79 Data.Aeson.Encoding.Internal
81 Data.Aeson.Encoding.Internal
80 Data.Aeson.Internal
82 Data.Aeson.Internal
@@ -86,7 +88,6 @@ library
86 Data.Aeson.Encode
88 Data.Aeson.Encode
87
89
88 other-modules:
90 other-modules:
89− Data.Aeson.Compat
90 Data.Aeson.Encoding.Builder
91 Data.Aeson.Encoding.Builder
91 Data.Aeson.Internal.Functions
92 Data.Aeson.Internal.Functions
92 Data.Aeson.Parser.Unescape
93 Data.Aeson.Parser.Unescape
@@ -99,45 +100,60 @@ library
99 Data.Attoparsec.Time
100 Data.Attoparsec.Time
100 Data.Attoparsec.Time.Internal
101 Data.Attoparsec.Time.Internal
101
102
103+ -- GHC bundled libs
102 build-depends:
104 build-depends:
103− attoparsec >= 0.13.0.1,
105+ base >= 4.7.0.0 && < 5,
104− base >= 4.5 && < 5,
106+ bytestring >= 0.10.4.0 && < 0.11,
105− base-compat >= 0.9.1 && < 0.11,
106− containers >= 0.4.2,
107+ containers >= 0.5.5.1 && < 0.7,
107− deepseq >= 1.3 && < 1.5,
108+ deepseq >= 1.3.0.0 && < 1.5,
108− dlist >= 0.6,
109− ghc-prim >= 0.2,
109+ ghc-prim >= 0.2 && < 0.6,
110− hashable >= 1.1.2.0,
111− scientific >= 0.3.4.7 && < 0.4,
112− tagged >=0.8.3 && <0.9,
113− template-haskell >= 2.7,
110+ template-haskell >= 2.9.0.0 && < 2.16,
114− text >= 1.2.3,
111+ text >= 1.2.3.0 && < 1.3,
115− th-abstraction >= 0.2.2 && < 0.3,
116− time >= 1.1.1.4,
112+ time >= 1.4 && < 1.10
117− time-locale-compat >= 0.1.1 && < 0.2,
118− unordered-containers >= 0.2.5.0 && < 0.3,
119− uuid-types >= 1.0.3 && <1.1,
120− vector >= 0.8
121
113
122− if flag(bytestring-builder)
114+ if impl(ghc >= 8.0)
123− build-depends: bytestring >= 0.9.2 && < 0.10.4,
115+ build-depends: bytestring >= 0.10.8.1
124− bytestring-builder >= 0.10.4 && < 1
116+
125− 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)
126− build-depends: bytestring >= 0.10.4 && < 0.11
123+ build-depends:
124+ contravariant >=1.4.1 && <1.6
127
125
128 if !impl(ghc >= 8.0)
126 if !impl(ghc >= 8.0)
129 -- `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
130 build-depends:
128 build-depends:
131− semigroups >= 0.18.2 && < 0.19,
129+ semigroups >= 0.18.5 && < 0.20,
132− transformers >= 0.2.2.0,
130+ transformers >= 0.3.0.0 && < 0.6,
133− transformers-compat >= 0.3,
131+ transformers-compat >= 0.6.2 && < 0.7,
134 fail == 4.9.*
132 fail == 4.9.*
135
133
136 if !impl(ghc >= 7.10)
134 if !impl(ghc >= 7.10)
137 -- `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
138− build-depends: nats >= 1 && < 1.2,
136+ build-depends: nats >= 1.1.1 && < 1.2,
139 void >= 0.7.2 && < 0.8
137 void >= 0.7.2 && < 0.8
140
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
156+
141 ghc-options: -Wall
157 ghc-options: -Wall
142
158
143 if flag(developer)
159 if flag(developer)
@@ -179,41 +195,49 @@ test-suite tests
179 Functions
195 Functions
180 Instances
196 Instances
181 Options
197 Options
198+ PropUtils
182 Properties
199 Properties
200+ PropertyGeneric
201+ PropertyKeys
202+ PropertyRoundTrip
203+ PropertyRTFunctors
204+ PropertyTH
183 SerializationFormatSpec
205 SerializationFormatSpec
184 Types
206 Types
185 UnitTests
207 UnitTests
186 UnitTests.NullaryConstructors
208 UnitTests.NullaryConstructors
187
209
188 build-depends:
210 build-depends:
189− QuickCheck >= 2.10.0.1 && < 2.12,
211+ QuickCheck >= 2.10.0.1 && < 2.14,
190 aeson,
212 aeson,
191 integer-logarithms >= 1 && <1.1,
213 integer-logarithms >= 1 && <1.1,
192 attoparsec,
214 attoparsec,
193 base,
215 base,
194 base-compat,
216 base-compat,
195− base-orphans >= 0.5.3 && <0.8,
217+ base-orphans >= 0.5.3 && <0.9,
196 base16-bytestring,
218 base16-bytestring,
197 containers,
219 containers,
198 directory,
220 directory,
199 dlist,
221 dlist,
222+ Diff >= 0.4 && < 0.5,
200 filepath,
223 filepath,
201− generic-deriving >= 1.10 && < 1.13,
224+ generic-deriving >= 1.10 && < 1.14,
202 ghc-prim >= 0.2,
225 ghc-prim >= 0.2,
203 hashable >= 1.2.4.0,
226 hashable >= 1.2.4.0,
204 scientific,
227 scientific,
205 tagged,
228 tagged,
206 template-haskell,
229 template-haskell,
207 tasty,
230 tasty,
231+ tasty-golden,
208 tasty-hunit,
232 tasty-hunit,
209 tasty-quickcheck,
233 tasty-quickcheck,
210 text,
234 text,
211 time,
235 time,
212− time-locale-compat,
236+ time-compat,
213 unordered-containers,
237 unordered-containers,
214 uuid-types,
238 uuid-types,
215 vector,
239 vector,
216− quickcheck-instances >= 0.3.16
240+ quickcheck-instances >= 0.3.21 && <0.4
217
241
218 if flag(bytestring-builder)
242 if flag(bytestring-builder)
219 build-depends: bytestring >= 0.9 && < 0.10.4,
243 build-depends: bytestring >= 0.9 && < 0.10.4,
@@ -223,7 +247,7 @@ test-suite tests
223
247
224 if !impl(ghc >= 8.0)
248 if !impl(ghc >= 8.0)
225 build-depends:
249 build-depends:
226− semigroups >= 0.18.2 && < 0.19,
250+ semigroups >= 0.18.2 && < 0.20,
227 transformers >= 0.2.2.0,
251 transformers >= 0.2.2.0,
228 transformers-compat >= 0.3
252 transformers-compat >= 0.3
229
253
attoparsec-iso8601/Data/Attoparsec/Time/Internal.hs+20−16
@@ -23,38 +23,42 @@ import Data.Int (Int64)
23import Data.Time
23import Data.Time
24import Unsafe.Coerce (unsafeCoerce)
24import Unsafe.Coerce (unsafeCoerce)
25
25
26−#if MIN_VERSION_base(4,7,0)
26+#if MIN_VERSION_time(1,6,0)
27
27
28−import Data.Fixed (Pico, Fixed(MkFixed))
28+import Data.Time.Clock (diffTimeToPicoseconds)
29
29
30−toPico :: Integer -> Pico
30+#endif
31−toPico = MkFixed
32
31
33−fromPico :: Pico -> Integer
32+import Data.Fixed (Pico, Fixed(MkFixed))
34−fromPico (MkFixed i) = i
35
33
36−#else
34+#if !MIN_VERSION_time(1,6,0)
37
35
38−import Data.Fixed (Pico)
36+diffTimeToPicoseconds :: DiffTime -> Integer
37+diffTimeToPicoseconds = unsafeCoerce
38+
39+#endif
39
40
40toPico :: Integer -> Pico
41toPico :: Integer -> Pico
41−toPico = unsafeCoerce
42+toPico = MkFixed
42
43
43fromPico :: Pico -> Integer
44fromPico :: Pico -> Integer
44−fromPico = unsafeCoerce
45+fromPico (MkFixed i) = i
45−
46−#endif
47
46
48-- | Like TimeOfDay, but using a fixed-width integer for seconds.
47-- | Like TimeOfDay, but using a fixed-width integer for seconds.
49data TimeOfDay64 = TOD {-# UNPACK #-} !Int
48data TimeOfDay64 = TOD {-# UNPACK #-} !Int
50 {-# UNPACK #-} !Int
49 {-# UNPACK #-} !Int
51 {-# UNPACK #-} !Int64
50 {-# UNPACK #-} !Int64
52
51
52+posixDayLength :: DiffTime
53+posixDayLength = 86400
54+
53diffTimeOfDay64 :: DiffTime -> TimeOfDay64
55diffTimeOfDay64 :: DiffTime -> TimeOfDay64
56+diffTimeOfDay64 t
57+ | t >= posixDayLength = TOD 23 59 (60000000000000 + pico (t - posixDayLength))
54−diffTimeOfDay64 t = TOD (fromIntegral h) (fromIntegral m) s
58+ | otherwise = TOD (fromIntegral h) (fromIntegral m) s
55− where (h,mp) = fromIntegral pico `quotRem` 3600000000000000
59+ where (h,mp) = pico t `quotRem` 3600000000000000
56− (m,s) = mp `quotRem` 60000000000000
60+ (m,s) = mp `quotRem` 60000000000000
57− pico = unsafeCoerce t :: Integer
61+ pico = fromIntegral . diffTimeToPicoseconds
58
62
59toTimeOfDay64 :: TimeOfDay -> TimeOfDay64
63toTimeOfDay64 :: TimeOfDay -> TimeOfDay64
60toTimeOfDay64 (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.11,
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/AesonEncode.hs+1−1
@@ -7,7 +7,7 @@ module Main (main) where
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/Compare.hs+8−2
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE ScopedTypeVariables #-}
3
4
@@ -6,16 +7,19 @@ module Main (main) where
6import Prelude.Compat
7import Prelude.Compat
7
8
8import Compare.BufferBuilder ()
9import Compare.BufferBuilder ()
9−import Compare.JsonBuilder ()
10import Criterion.Main
10import Criterion.Main
11import Data.BufferBuilder.Json
11import Data.BufferBuilder.Json
12−import Data.Json.Builder
13import Twitter
12import Twitter
14import Twitter.Manual ()
13import Twitter.Manual ()
15import Typed.Common
14import Typed.Common
16import qualified Data.Aeson as Aeson
15import qualified Data.Aeson as Aeson
17import qualified Compare.JsonBench as JsonBench
16import qualified Compare.JsonBench as JsonBench
18
17
18+#ifdef MIN_VERSION_json_builder
19+import Data.Json.Builder
20+import Compare.JsonBuilder ()
21+#endif
22+
19main :: IO ()
23main :: IO ()
20main =
24main =
21 defaultMain [
25 defaultMain [
@@ -23,7 +27,9 @@ main =
23 bgroup "twitter" [
27 bgroup "twitter" [
24 bench "aeson" $ nf Aeson.encode twtr
28 bench "aeson" $ nf Aeson.encode twtr
25 , bench "buffer-builder" $ nf encodeJson twtr
29 , bench "buffer-builder" $ nf encodeJson twtr
30+#ifdef MIN_VERSION_json_builder
26 , bench "json-builder" $ nf toJsonLBS twtr
31 , bench "json-builder" $ nf toJsonLBS twtr
32+#endif
27 ]
33 ]
28 , JsonBench.benchmarks
34 , JsonBench.benchmarks
29 ]
35 ]
benchmarks/Compare/JsonBench.hs+12−3
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE MagicHash #-}
3{-# LANGUAGE MagicHash #-}
3{-# LANGUAGE NoImplicitPrelude #-}
4{-# LANGUAGE NoImplicitPrelude #-}
@@ -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/CompareWithJSON.hs+1−1
@@ -27,7 +27,7 @@ instance (NFData v) => NFData (J.JSObject v) where
27instance NFData J.JSValue where
27instance NFData J.JSValue where
28 rnf J.JSNull = ()
28 rnf J.JSNull = ()
29 rnf (J.JSBool b) = rnf b
29 rnf (J.JSBool b) = rnf b
30− rnf (J.JSRational a b) = rnf a `seq` rnf b `seq` ()
30+ rnf (J.JSRational a b) = rnf a `seq` rnf b
31 rnf (J.JSString s) = rnf (J.fromJSString s)
31 rnf (J.JSString s) = rnf (J.fromJSString s)
32 rnf (J.JSArray lst) = rnf lst
32 rnf (J.JSArray lst) = rnf lst
33 rnf (J.JSObject o) = rnf o
33 rnf (J.JSObject o) = rnf o
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+1−1
@@ -17,7 +17,7 @@ import Text.JSON
17instance NFData JSValue where
17instance NFData JSValue where
18 rnf JSNull = ()
18 rnf JSNull = ()
19 rnf (JSBool b) = rnf b
19 rnf (JSBool b) = rnf b
20− rnf (JSRational b r) = rnf b `seq` rnf r `seq` ()
20+ rnf (JSRational b r) = rnf b `seq` rnf r
21 rnf (JSString s) = rnf (fromJSString s)
21 rnf (JSString s) = rnf (fromJSString s)
22 rnf (JSArray vs) = rnf vs
22 rnf (JSArray vs) = rnf vs
23 rnf (JSObject kvs) = rnf (fromJSObject kvs)
23 rnf (JSObject kvs) = rnf (fromJSObject kvs)
benchmarks/aeson-benchmarks.cabal+52−26
@@ -28,39 +28,42 @@ library
28 hs-source-dirs: .. ../ffi ../pure ../attoparsec-iso8601
28 hs-source-dirs: .. ../ffi ../pure ../attoparsec-iso8601
29 c-sources: ../cbits/unescape_string.c
29 c-sources: ../cbits/unescape_string.c
30 build-depends:
30 build-depends:
31− attoparsec >= 0.13.0.1,
31+ attoparsec,
32− base == 4.*,
32+ base,
33− base-compat >= 0.9.1 && <0.11,
33+ base-compat,
34− time-locale-compat >=0.1.1 && <0.2,
35 containers,
34 containers,
36 deepseq,
35 deepseq,
37− dlist >= 0.2,
36+ dlist,
38− fail == 4.9.*,
37+ fail,
39− ghc-prim >= 0.2,
38+ ghc-prim,
40− hashable >= 1.1.2.0,
39+ hashable,
41 mtl,
40 mtl,
41+ primitive,
42− scientific >= 0.3.4.7 && < 0.4,
42+ scientific,
43 syb,
43 syb,
44− tagged >=0.8.3 && <0.9,
44+ tagged,
45− template-haskell >= 2.4,
45+ template-haskell,
46− text >= 1.2.3,
46+ text,
47− th-abstraction >= 0.2.2 && < 0.3,
47+ th-abstraction,
48+ time-compat,
48 time,
49 time,
49 transformers,
50 transformers,
50− unordered-containers >= 0.2.3.0,
51+ unordered-containers,
51− uuid-types >= 1.0.3 && <1.1,
52+ uuid-types,
52− vector >= 0.7.1
53+ vector
53
54
54− if !impl(ghc >= 7.10)
55+ if !impl(ghc >= 8.6)
55− -- `Numeric.Natural` is available in base only since GHC 7.10 / base 4.8
56− build-depends: nats >= 1 && < 1.2
56+ build-depends: contravariant
57−
58− if impl(ghc >=7.8)
59− cpp-options: -DHAS_COERCIBLE
60
57
61 if !impl(ghc >= 8.0)
58 if !impl(ghc >= 8.0)
62 -- `Data.Semigroup` is available in base only since GHC 8.0 / base 4.9
59 -- `Data.Semigroup` is available in base only since GHC 8.0 / base 4.9
63− build-depends: semigroups >= 0.18.2 && < 0.19
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
64
67
65 include-dirs: ../include
68 include-dirs: ../include
66
69
@@ -69,7 +72,6 @@ library
69
72
70 exposed-modules:
73 exposed-modules:
71 Data.Aeson
74 Data.Aeson
72− Data.Aeson.Compat
73 Data.Aeson.Encoding
75 Data.Aeson.Encoding
74 Data.Aeson.Encoding.Builder
76 Data.Aeson.Encoding.Builder
75 Data.Aeson.Encoding.Internal
77 Data.Aeson.Encoding.Internal
@@ -154,7 +156,6 @@ executable aeson-benchmark-compare
154 other-modules:
156 other-modules:
155 Compare.BufferBuilder
157 Compare.BufferBuilder
156 Compare.JsonBench
158 Compare.JsonBench
157− Compare.JsonBuilder
158 Twitter
159 Twitter
159 Twitter.Manual
160 Twitter.Manual
160 Typed.Common
161 Typed.Common
@@ -167,9 +168,17 @@ executable aeson-benchmark-compare
167 criterion >= 1.0,
168 criterion >= 1.0,
168 deepseq,
169 deepseq,
169 ghc-prim,
170 ghc-prim,
170− json-builder,
171 text
171 text
172
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+
173executable aeson-benchmark-micro
182executable aeson-benchmark-micro
174 default-language: Haskell2010
183 default-language: Haskell2010
175 main-is: Micro.hs
184 main-is: Micro.hs
@@ -215,6 +224,8 @@ executable aeson-benchmark-typed
215 bytestring-builder >= 0.10.4 && < 1
224 bytestring-builder >= 0.10.4 && < 1
216 else
225 else
217 build-depends: bytestring >= 0.10.4
226 build-depends: bytestring >= 0.10.4
227+ if impl(ghc < 8.0)
228+ build-depends: semigroups
218
229
219executable aeson-benchmark-compare-with-json
230executable aeson-benchmark-compare-with-json
220 default-language: Haskell2010
231 default-language: Haskell2010
@@ -317,3 +328,18 @@ executable aeson-benchmark-foldable
317 text,
328 text,
318 unordered-containers,
329 unordered-containers,
319 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+4−1
@@ -1,2 +1,5 @@
1with-compiler: ghc
1with-compiler: ghc
2+packages: .
2−packages: ., attoparsec-iso8601, benchmarks
3+packages: attoparsec-iso8601
4+-- packages: benchmarks
5+tests: true
changelog.md+89−2
@@ -1,6 +1,93 @@
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
4+
5+* `FromJSON` instance of `Ratio a` now parses numbers in addtion to
6+ standard `{numerator=..., denumerator=...}` encoding.
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+
3−### 1.4.0.0
90+## 1.4.0.0
4
91
5This 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.
92This 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.
6
93
@@ -19,7 +106,7 @@ For the same reasons the following instances & functions have been removed:
19
106
20Finally, encoding integral values with large exponents now uses scientific notation, this saves space for large numbers.
107Finally, encoding integral values with large exponents now uses scientific notation, this saves space for large numbers.
21
108
22−### 1.3.1.1
109+#### 1.3.1.1
23
110
24* Catch 0 denominators when parsing Ratio
111* Catch 0 denominators when parsing Ratio
25
112
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−3
@@ -1,4 +1,4 @@
1−resolver: nightly-2018-03-12
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,5 +9,4 @@ resolver: nightly-2018-03-12
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.3.1.0
stack-ffi-unescape.yaml+2−2
@@ -1,4 +1,4 @@
1−resolver: lts-10.0
1+resolver: nightly-2019-09-21
2packages:
2packages:
3- '.'
3- '.'
4flags:
4flags:
@@ -6,4 +6,4 @@ flags:
6 fast: true
6 fast: true
7 cffi: true
7 cffi: true
8extra-deps:
8extra-deps:
9−- text-1.2.3.0
9+- Diff-0.4.0
stack-lts10.yamldeleted−9
@@ -1,9 +0,0 @@
1−resolver: lts-10.0
2−packages:
3−- '.'
4−flags:
5− aeson:
6− fast: true
7− cffi: false
8− attoparsec-iso8601:
9− fast: true
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−23
@@ -1,23 +0,0 @@
1−resolver: lts-6.35
2−packages:
3−- '.'
4−- attoparsec-iso8601
5−extra-deps:
6−- integer-logarithms-1
7−- semigroups-0.18.2
8−- tagged-0.8.5
9−- transformers-compat-0.5.1.4
10−- QuickCheck-2.10.0.1
11−- base-compat-0.9.3
12−- hashable-1.2.6.1
13−- integer-logarithms-1
14−- quickcheck-instances-0.3.16
15−- th-abstraction-0.2.2.0
16−flags:
17−flags:
18− aeson:
19− fast: true
20− attoparsec-iso8601:
21− fast: true
22− semigroups:
23− bytestring-builder: false
stack-lts7.yamldeleted−16
@@ -1,16 +0,0 @@
1−resolver: lts-7.24
2−packages:
3−- '.'
4−- attoparsec-iso8601
5−extra-deps:
6−- QuickCheck-2.10.0.1
7−- base-compat-0.9.3
8−- hashable-1.2.6.1
9−- integer-logarithms-1
10−- quickcheck-instances-0.3.16
11−- th-abstraction-0.2.2.0
12−flags:
13− aeson:
14− fast: true
15− attoparsec-iso8601:
16− fast: true
stack-lts8.yamldeleted−14
@@ -1,14 +0,0 @@
1−resolver: lts-8.24
2−packages:
3−- '.'
4−- attoparsec-iso8601
5−extra-deps:
6−- QuickCheck-2.10.0.1
7−- base-compat-0.9.3
8−- quickcheck-instances-0.3.16
9−- th-abstraction-0.2.2.0
10−flags:
11− aeson:
12− fast: true
13− attoparsec-iso8601:
14− fast: true
stack-lts9.yamldeleted−12
@@ -1,12 +0,0 @@
1−resolver: lts-9.9
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-2018-03-07
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
tests/DataFamilies/Properties.hs+2−1
@@ -7,7 +7,8 @@ import 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
12import Test.Tasty (TestTree, testGroup)
13import Test.Tasty (TestTree, testGroup)
13import Test.Tasty.QuickCheck (testProperty)
14import Test.Tasty.QuickCheck (testProperty)
tests/Encoders.hs+48−6
@@ -6,6 +6,7 @@
6module Encoders (module Encoders) where
6module Encoders (module Encoders) where
7
7
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
@@ -236,6 +243,31 @@ gSomeTypeToEncodingOmitNothingFields :: SomeType Int -> Encoding
236gSomeTypeToEncodingOmitNothingFields = genericToEncoding optsOmitNothingFields
243gSomeTypeToEncodingOmitNothingFields = genericToEncoding optsOmitNothingFields
237
244
238
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+
239--------------------------------------------------------------------------------
271--------------------------------------------------------------------------------
240-- Option fields
272-- Option fields
241--------------------------------------------------------------------------------
273--------------------------------------------------------------------------------
@@ -414,3 +446,13 @@ gOneConstructorToEncodingTagged = genericToEncoding optsTagSingleConstructors
414
446
415gOneConstructorParseJSONTagged :: Value -> Parser OneConstructor
447gOneConstructorParseJSONTagged :: Value -> Parser OneConstructor
416gOneConstructorParseJSONTagged = 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+205−51
@@ -9,62 +9,216 @@ module ErrorMessages
9
9
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.Tasty (TestTree)
22+import Test.Tasty (TestTree, TestName)
17−import Test.Tasty.HUnit (Assertion, assertFailure, assertEqual, testCase)
23+import Test.Tasty.Golden.Advanced (goldenTest)
18import qualified Data.ByteString.Lazy.Char8 as L
24import qualified Data.ByteString.Lazy.Char8 as L
19import qualified Data.HashMap.Strict as HM
25import qualified Data.HashMap.Strict as HM
20
26
27+import Encoders
28+import Types
29+
21tests :: [TestTree]
30tests :: [TestTree]
22tests =
31tests =
23− [
32+ [ aesonGoldenTest "simple" "tests/golden/simple.expected" output
24− testCase "Int" int
33+ , aesonGoldenTest "generic" "tests/golden/generic.expected" (outputGeneric G)
25− , testCase "Integer" integer
26− , testCase "Natural" natural
27− , testCase "String" string
28− , testCase "HashMap" hashMap
29− ]
34+ ]
30−
35+
31−int :: Assertion
36+output :: Output
32−int = do
37+output = concat
33− let t = test (Proxy :: Proxy Int)
38+ [ testFor "Int" (Proxy :: Proxy Int)
34− t "\"\"" $ expected "Int" "String"
39+ [ "\"\""
35− t "[]" $ expected "Int" "Array"
40+ , "[]"
36− t "{}" $ expected "Int" "Object"
41+ , "{}"
37− t "null" $ expected "Int" "Null"
42+ , "null"
38−
43+ ]
39−integer :: Assertion
44+
40−integer = do
41− let t = test (Proxy :: Proxy Integer)
45+ , testFor "Integer" (Proxy :: Proxy Integer)
42− t "44.44" $ expected "Integer" "floating number 44.44"
46+ [ "44.44"
43−
47+ ]
44−natural :: Assertion
48+
45−natural = do
46− let t = test (Proxy :: Proxy Natural)
49+ , testFor "Natural" (Proxy :: Proxy Natural)
47− t "44.44" $ expected "Natural" "floating number 44.44"
50+ [ "44.44"
48− t "-50" $ expected "Natural" "negative number -50"
51+ , "-50"
49−
52+ ]
50−string :: Assertion
53+
51−string = do
52− let t = test (Proxy :: Proxy String)
54+ , testFor "String" (Proxy :: Proxy String)
53− t "1" $ expected "String" "Number"
55+ [ "1"
54− t "[]" $ expected "String" "Array"
56+ , "[]"
55− t "{}" $ expected "String" "Object"
57+ , "{}"
56− t "null" $ expected "String" "Null"
58+ , "null"
57−
59+ ]
58−hashMap :: Assertion
60+
59−hashMap = do
60− let t = test (Proxy :: Proxy (HM.HashMap String Int))
61+ , testFor "HashMap" (Proxy :: Proxy (HM.HashMap String Int))
61− t "\"\"" $ expected "HashMap k v" "String"
62+ [ "\"\""
62− t "[]" $ expected "HashMap k v" "Array"
63+ , "[]"
63−
64+ ]
64−expected :: String -> String -> String
65+
65−expected ex enc = "Error in $: expected " ++ ex ++ ", encountered " ++ enc
66+ -- issue #356
66−
67+ , testFor "Either" (Proxy :: Proxy (Int, Either (Int, Bool) ()))
67−test :: forall a proxy . (FromJSON a, Show a) => proxy a -> L.ByteString -> String -> Assertion
68+ [ "[1,{\"Left\":[2,3]}]"
68−test _ v msg = case eitherDecode v of
69+ ]
69− Left e -> assertEqual "Invalid error message" msg e
70+
70− 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/Instances.hs+1−4
@@ -1,4 +1,3 @@
1−{-# LANGUAGE CPP #-}
2{-# LANGUAGE FlexibleContexts #-}
1{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleInstances #-}
2{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE NoImplicitPrelude #-}
3{-# LANGUAGE NoImplicitPrelude #-}
@@ -25,14 +24,12 @@ import qualified Data.HashMap.Strict as HM
25
24
26import Data.Orphans ()
25import Data.Orphans ()
27import Test.QuickCheck.Instances ()
26import Test.QuickCheck.Instances ()
28−#if MIN_VERSION_base(4,7,0)
29import Data.Hashable.Time ()
27import Data.Hashable.Time ()
30−#endif
31
28
32-- "System" types.
29-- "System" types.
33
30
34instance Arbitrary DotNetTime where
31instance Arbitrary DotNetTime where
35− arbitrary = DotNetTime `liftM` arbitrary
32+ arbitrary = DotNetTime `fmap` arbitrary
36 shrink = map DotNetTime . shrink . fromDotNetTime
33 shrink = map DotNetTime . shrink . fromDotNetTime
37
34
38-- | Compare timezone part only on 'timeZoneMinutes'
35-- | Compare timezone part only on 'timeZoneMinutes'
tests/Options.hs+11
@@ -55,3 +55,14 @@ optsOptionField = optsDefault
55 { fieldLabelModifier = const "field"
55 { fieldLabelModifier = const "field"
56 , omitNothingFields = True
56 , omitNothingFields = True
57 }
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+10−528
@@ -1,231 +1,18 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE FlexibleContexts #-} -- For arbitrary Compose
3{-# LANGUAGE NoImplicitPrelude #-}
1{-# LANGUAGE NoImplicitPrelude #-}
4−{-# LANGUAGE OverloadedStrings #-}
5−{-# LANGUAGE RankNTypes #-}
6−{-# LANGUAGE ScopedTypeVariables #-}
7
2
8module Properties (module Properties) where
3module Properties (module Properties) where
9
4
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.Semigroup (Option(..))
29−import Data.Sequence (Seq)
30−import Data.Tagged (Tagged)
31−import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)
32−import Data.Version (Version)
33−import Encoders
34import Instances ()
7import Instances ()
35−import Numeric.Natural (Natural)
36import Test.Tasty (TestTree, testGroup)
8import Test.Tasty (TestTree, testGroup)
37import Test.Tasty.QuickCheck (testProperty)
9import Test.Tasty.QuickCheck (testProperty)
38−import Test.QuickCheck (Arbitrary(..), Property, Testable, (===), (.&&.), counterexample)
10+import Test.QuickCheck (Property)
39−import Types
11+import PropUtils
40−import qualified Data.Attoparsec.Lazy as L
12+import PropertyGeneric
41−import qualified Data.ByteString.Lazy.Char8 as L
13+import PropertyKeys
42−import qualified Data.HashMap.Strict as H
14+import PropertyRoundTrip
43−import qualified Data.Map as Map
15+import PropertyTH
44−import qualified Data.Text as T
45−import qualified Data.Text.Lazy as LT
46−import qualified Data.UUID.Types as UUID
47−import qualified Data.Vector as V
48−
49−encodeDouble :: Double -> Double -> Property
50−encodeDouble num denom
51− | isInfinite d || isNaN d = encode d === "null"
52− | otherwise = (read . L.unpack . encode) d === d
53− where d = num / denom
54−
55−encodeInteger :: Integer -> Property
56−encodeInteger i = encode i === L.pack (show i)
57−
58−toParseJSON :: (Eq a, Show a) =>
59− (Value -> Parser a) -> (a -> Value) -> a -> Property
60−toParseJSON parsejson tojson x =
61− case iparse parsejson . tojson $ x of
62− IError path msg -> failure "parse" (formatError path msg) x
63− ISuccess x' -> x === x'
64−
65−toParseJSON1
66− :: (Eq (f Int), Show (f Int))
67− => (forall a. LiftParseJSON f a)
68− -> (forall a. LiftToJSON f a)
69− -> f Int
70− -> Property
71−toParseJSON1 parsejson1 tojson1 = toParseJSON parsejson tojson
72− where
73− parsejson = parsejson1 parseJSON (listParser parseJSON)
74− tojson = tojson1 toJSON (listValue toJSON)
75−
76−roundTripEnc :: (FromJSON a, ToJSON a, Show a) =>
77− (a -> a -> Property) -> a -> a -> Property
78−roundTripEnc eq _ i =
79− case fmap ifromJSON . L.parse value . encode $ i of
80− L.Done _ (ISuccess v) -> v `eq` i
81− L.Done _ (IError path err) -> failure "fromJSON" (formatError path err) i
82− L.Fail _ _ err -> failure "parse" err i
83−
84−roundTripNoEnc :: (FromJSON a, ToJSON a, Show a) =>
85− (a -> a -> Property) -> a -> a -> Property
86−roundTripNoEnc eq _ i =
87− case ifromJSON . toJSON $ i of
88− (ISuccess v) -> v `eq` i
89− (IError path err) -> failure "fromJSON" (formatError path err) i
90−
91−roundTripEq :: (Eq a, FromJSON a, ToJSON a, Show a) => a -> a -> Property
92−roundTripEq x y = roundTripEnc (===) x y .&&. roundTripNoEnc (===) x y
93−
94−-- We test keys by encoding HashMap and Map with it
95−roundTripKey
96− :: (Ord a, Hashable a, FromJSONKey a, ToJSONKey a, Show a)
97− => a -> HashMap a Int -> Map a Int -> Property
98−roundTripKey _ h m = roundTripEq h h .&&. roundTripEq m m
99−
100−infix 4 ==~
101−(==~) :: (ApproxEq a, Show a) => a -> a -> Property
102−x ==~ y =
103− counterexample (show x ++ " /= " ++ show y) (x =~ y)
104−
105−toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a, Show a) => a -> Property
106−toFromJSON x = case ifromJSON (toJSON x) of
107− IError path err -> failure "fromJSON" (formatError path err) x
108− ISuccess x' -> x === x'
109−
110−modifyFailureProp :: String -> String -> Bool
111−modifyFailureProp orig added =
112− result == Error (added ++ orig)
113− where
114− parser = const $ modifyFailure (added ++) $ fail orig
115− result :: Result ()
116− result = parse parser ()
117−
118−parserThrowErrorProp :: String -> Property
119−parserThrowErrorProp msg =
120− result === Error msg
121− where
122− parser = const $ parserThrowError [] msg
123− result :: Result ()
124− result = parse parser ()
125−
126−-- | Tests (also) that we catch the JSONPath and it has elements in the right order.
127−parserCatchErrorProp :: [String] -> String -> Property
128−parserCatchErrorProp path msg =
129− result === Success ([I.Key "outer", I.Key "inner"] ++ jsonPath, msg)
130− where
131− parser = parserCatchError outer (curry pure)
132−
133− outer = inner I.<?> I.Key "outer"
134− inner = parserThrowError jsonPath msg I.<?> I.Key "inner"
135−
136− result :: Result (I.JSONPath, String)
137− result = parse (const parser) ()
138−
139− jsonPath = map (I.Key . T.pack) path
140−
141−-- | Perform a structural comparison of the results of two encoding
142−-- methods. Compares decoded values to account for HashMap-driven
143−-- variation in JSON object key ordering.
144−sameAs :: (a -> Value) -> (a -> Encoding) -> a -> Property
145−sameAs toVal toEnc v =
146− counterexample (show s) $
147− eitherDecode s === Right (toVal v)
148− where
149− s = encodingToLazyByteString (toEnc v)
150−
151−sameAs1
152− :: (forall a. LiftToJSON f a)
153− -> (forall a. LiftToEncoding f a)
154− -> f Int
155− -> Property
156−sameAs1 toVal1 toEnc1 v = lhs === rhs
157− where
158− rhs = Right $ toVal1 toJSON (listValue toJSON) v
159− lhs = eitherDecode . encodingToLazyByteString $
160− toEnc1 toEncoding (listEncoding toEncoding) v
161−
162−sameAs1Agree
163− :: ToJSON a
164− => (f a -> Encoding)
165− -> (forall b. LiftToEncoding f b)
166− -> f a
167− -> Property
168−sameAs1Agree toEnc toEnc1 v = rhs === lhs
169− where
170− rhs = encodingToLazyByteString $ toEnc v
171− lhs = encodingToLazyByteString $ toEnc1 toEncoding (listEncoding toEncoding) v
172−
173−type P6 = Product6 Int Bool String (Approx Double) (Int, Approx Double) ()
174−type S4 = Sum4 Int8 ZonedTime T.Text (Map.Map String Int)
175−
176−--------------------------------------------------------------------------------
177−-- Value properties
178−--------------------------------------------------------------------------------
179−
180−-- | Add the formatted @Value@ to the printed counterexample when the property
181−-- fails.
182−checkValue :: Testable a => (Value -> a) -> Value -> Property
183−checkValue prop v = counterexample (L.unpack (encode v)) (prop v)
184−
185−isString :: Value -> Bool
186−isString (String _) = True
187−isString _ = False
188−
189−is2ElemArray :: Value -> Bool
190−is2ElemArray (Array v) = V.length v == 2 && isString (V.head v)
191−is2ElemArray _ = False
192−
193−isTaggedObjectValue :: Value -> Bool
194−isTaggedObjectValue (Object obj) = "tag" `H.member` obj &&
195− "contents" `H.member` obj
196−isTaggedObjectValue _ = False
197−
198−isNullaryTaggedObject :: Value -> Bool
199−isNullaryTaggedObject obj = isTaggedObject' obj && isObjectWithSingleField obj
200−
201−isTaggedObject :: Value -> Property
202−isTaggedObject = checkValue isTaggedObject'
203−
204−isTaggedObject' :: Value -> Bool
205−isTaggedObject' (Object obj) = "tag" `H.member` obj
206−isTaggedObject' _ = False
207−
208−isObjectWithSingleField :: Value -> Bool
209−isObjectWithSingleField (Object obj) = H.size obj == 1
210−isObjectWithSingleField _ = False
211−
212−-- | is untaggedValue of EitherTextInt
213−isUntaggedValueETI :: Value -> Bool
214−isUntaggedValueETI (String s)
215− | s == "nonenullary" = True
216−isUntaggedValueETI (Bool _) = True
217−isUntaggedValueETI (Number _) = True
218−isUntaggedValueETI (Array a) = length a == 2
219−isUntaggedValueETI _ = False
220−
221−isEmptyArray :: Value -> Property
222−isEmptyArray = checkValue isEmptyArray'
223−
224−isEmptyArray' :: Value -> Bool
225−isEmptyArray' = (Array mempty ==)
226−
227−
228−--------------------------------------------------------------------------------
229
16
230
17
231tests :: TestTree
18tests :: TestTree
@@ -234,98 +21,8 @@ tests = testGroup "properties" [
234 testProperty "encodeDouble" encodeDouble
21 testProperty "encodeDouble" encodeDouble
235 , testProperty "encodeInteger" encodeInteger
22 , testProperty "encodeInteger" encodeInteger
236 ]
23 ]
237− , testGroup "roundTrip" [
24+ , roundTripTests
238− testProperty "Bool" $ roundTripEq True
25+ , keysTests
239− , testProperty "Double" $ roundTripEq (1 :: Approx Double)
240− , testProperty "Int" $ roundTripEq (1 :: Int)
241− , testProperty "NonEmpty Char" $ roundTripEq (undefined :: NonEmpty Char)
242− , testProperty "Integer" $ roundTripEq (1 :: Integer)
243− , testProperty "String" $ roundTripEq ("" :: String)
244− , testProperty "Text" $ roundTripEq T.empty
245− , testProperty "Lazy Text" $ roundTripEq LT.empty
246− , testProperty "Foo" $ roundTripEq (undefined :: Foo)
247− , testProperty "Day" $ roundTripEq (undefined :: Day)
248− , testProperty "BCE Day" $ roundTripEq (undefined :: BCEDay)
249− , testProperty "DotNetTime" $ roundTripEq (undefined :: Approx DotNetTime)
250− , testProperty "LocalTime" $ roundTripEq (undefined :: LocalTime)
251− , testProperty "TimeOfDay" $ roundTripEq (undefined :: TimeOfDay)
252− , testProperty "UTCTime" $ roundTripEq (undefined :: UTCTime)
253− , testProperty "ZonedTime" $ roundTripEq (undefined :: ZonedTime)
254− , testProperty "NominalDiffTime" $ roundTripEq (undefined :: NominalDiffTime)
255− , testProperty "DiffTime" $ roundTripEq (undefined :: DiffTime)
256− , testProperty "Version" $ roundTripEq (undefined :: Version)
257− , testProperty "Natural" $ roundTripEq (undefined :: Natural)
258− , testProperty "Proxy" $ roundTripEq (undefined :: Proxy Int)
259− , testProperty "Tagged" $ roundTripEq (undefined :: Tagged Int Char)
260− , testProperty "Const" $ roundTripEq (undefined :: Const Int Char)
261− , testProperty "DList" $ roundTripEq (undefined :: DList Int)
262− , testProperty "Seq" $ roundTripEq (undefined :: Seq Int)
263− , testProperty "Rational" $ roundTripEq (undefined :: Rational)
264− , testProperty "Ratio Int" $ roundTripEq (undefined :: Ratio Int)
265− , testProperty "UUID" $ roundTripEq UUID.nil
266− , testGroup "functors"
267− [ testProperty "Identity Char" $ roundTripEq (undefined :: I Int)
268−
269− , testProperty "Identity Char" $ roundTripEq (undefined :: I Char)
270− , testProperty "Identity [Char]" $ roundTripEq (undefined :: I String)
271− , testProperty "[Identity Char]" $ roundTripEq (undefined :: [I Char])
272−
273− , testProperty "Compose I I Int" $ roundTripEq (undefined :: LogScaled (Compose I I Int))
274− , testProperty "Compose [] I Int" $ roundTripEq (undefined :: LogScaled (Compose [] I Int))
275− , testProperty "Compose I [] Int" $ roundTripEq (undefined :: LogScaled (Compose I [] Int))
276− , testProperty "Compose [] [] Int" $ roundTripEq (undefined :: LogScaled (Compose [] [] Int))
277−
278− , testProperty "Compose I I Char" $ roundTripEq (undefined :: LogScaled (Compose I I Char))
279− , testProperty "Compose [] I Char" $ roundTripEq (undefined :: LogScaled (Compose [] I Char))
280− , testProperty "Compose I [] Char" $ roundTripEq (undefined :: LogScaled (Compose I [] Char))
281− , testProperty "Compose [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose [] [] Char))
282−
283− , testProperty "Compose3 I I I Char" $ roundTripEq (undefined :: LogScaled (Compose3 I I I Char))
284− , testProperty "Compose3 I [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3 I [] I Char))
285− , testProperty "Compose3 I I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 I I [] Char))
286− , testProperty "Compose3 I [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 I [] [] Char))
287− , testProperty "Compose3 [] I I Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] I I Char))
288− , testProperty "Compose3 [] [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] [] I Char))
289− , testProperty "Compose3 [] I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] I [] Char))
290− , testProperty "Compose3 [] [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3 [] [] [] Char))
291−
292− , testProperty "Compose3' I I I Char" $ roundTripEq (undefined :: LogScaled (Compose3' I I I Char))
293− , testProperty "Compose3' I [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3' I [] I Char))
294− , testProperty "Compose3' I I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' I I [] Char))
295− , testProperty "Compose3' I [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' I [] [] Char))
296− , testProperty "Compose3' [] I I Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] I I Char))
297− , testProperty "Compose3' [] [] I Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] [] I Char))
298− , testProperty "Compose3' [] I [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] I [] Char))
299− , testProperty "Compose3' [] [] [] Char" $ roundTripEq (undefined :: LogScaled (Compose3' [] [] [] Char))
300− ]
301− , testGroup "ghcGenerics" [
302− testProperty "OneConstructor" $ roundTripEq OneConstructor
303− , testProperty "Product2" $ roundTripEq (undefined :: Product2 Int Bool)
304− , testProperty "Product6" $ roundTripEq (undefined :: P6)
305− , testProperty "Sum4" $ roundTripEq (undefined :: S4)
306− ]
307− ]
308− , testGroup "roundTrip Key"
309− [ testProperty "Bool" $ roundTripKey True
310− , testProperty "Text" $ roundTripKey (undefined :: T.Text)
311− , testProperty "String" $ roundTripKey (undefined :: String)
312− , testProperty "Int" $ roundTripKey (undefined :: Int)
313− , testProperty "[Text]" $ roundTripKey (undefined :: LogScaled [T.Text])
314− , testProperty "(Int,Char)" $ roundTripKey (undefined :: (Int,Char))
315− , testProperty "Integer" $ roundTripKey (undefined :: Integer)
316− , testProperty "Natural" $ roundTripKey (undefined :: Natural)
317− , testProperty "Float" $ roundTripKey (undefined :: Float)
318− , testProperty "Double" $ roundTripKey (undefined :: Double)
319−#if MIN_VERSION_base(4,7,0)
320− , testProperty "Day" $ roundTripKey (undefined :: Day)
321− , testProperty "LocalTime" $ roundTripKey (undefined :: LocalTime)
322− , testProperty "TimeOfDay" $ roundTripKey (undefined :: TimeOfDay)
323− , testProperty "UTCTime" $ roundTripKey (undefined :: UTCTime)
324−#endif
325− , testProperty "Version" $ roundTripKey (undefined :: Version)
326− , testProperty "Lazy Text" $ roundTripKey (undefined :: LT.Text)
327− , testProperty "UUID" $ roundTripKey UUID.nil
328− ]
329 , testGroup "toFromJSON" [
26 , testGroup "toFromJSON" [
330 testProperty "Integer" (toFromJSON :: Integer -> Property)
27 testProperty "Integer" (toFromJSON :: Integer -> Property)
331 , testProperty "Double" (toFromJSON :: Double -> Property)
28 , testProperty "Double" (toFromJSON :: Double -> Property)
@@ -338,221 +35,6 @@ tests = testGroup "properties" [
338 , testProperty "parserThrowError" parserThrowErrorProp
35 , testProperty "parserThrowError" parserThrowErrorProp
339 , testProperty "parserCatchError" parserCatchErrorProp
36 , testProperty "parserCatchError" parserCatchErrorProp
340 ]
37 ]
341− , testGroup "generic" [
38+ , genericTests
342− testGroup "toJSON" [
39+ , templateHaskellTests
343− testGroup "Nullary" [
344− testProperty "string" (isString . gNullaryToJSONString)
345− , testProperty "2ElemArray" (is2ElemArray . gNullaryToJSON2ElemArray)
346− , testProperty "TaggedObject" (isNullaryTaggedObject . gNullaryToJSONTaggedObject)
347− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . gNullaryToJSONObjectWithSingleField)
348− , testGroup "roundTrip" [
349− testProperty "string" (toParseJSON gNullaryParseJSONString gNullaryToJSONString)
350− , testProperty "2ElemArray" (toParseJSON gNullaryParseJSON2ElemArray gNullaryToJSON2ElemArray)
351− , testProperty "TaggedObject" (toParseJSON gNullaryParseJSONTaggedObject gNullaryToJSONTaggedObject)
352− , testProperty "ObjectWithSingleField" (toParseJSON gNullaryParseJSONObjectWithSingleField gNullaryToJSONObjectWithSingleField)
353− ]
354− ]
355− , testGroup "EitherTextInt" [
356− testProperty "UntaggedValue" (isUntaggedValueETI . gEitherTextIntToJSONUntaggedValue)
357− , testProperty "roundtrip" (toParseJSON gEitherTextIntParseJSONUntaggedValue gEitherTextIntToJSONUntaggedValue)
358− ]
359− , testGroup "SomeType" [
360− testProperty "2ElemArray" (is2ElemArray . gSomeTypeToJSON2ElemArray)
361− , testProperty "TaggedObject" (isTaggedObject . gSomeTypeToJSONTaggedObject)
362− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . gSomeTypeToJSONObjectWithSingleField)
363− , testGroup "roundTrip" [
364− testProperty "2ElemArray" (toParseJSON gSomeTypeParseJSON2ElemArray gSomeTypeToJSON2ElemArray)
365− , testProperty "TaggedObject" (toParseJSON gSomeTypeParseJSONTaggedObject gSomeTypeToJSONTaggedObject)
366− , testProperty "ObjectWithSingleField" (toParseJSON gSomeTypeParseJSONObjectWithSingleField gSomeTypeToJSONObjectWithSingleField)
367−
368−#if __GLASGOW_HASKELL__ >= 706
369− , testProperty "2ElemArray unary" (toParseJSON1 gSomeTypeLiftParseJSON2ElemArray gSomeTypeLiftToJSON2ElemArray)
370− , testProperty "TaggedObject unary" (toParseJSON1 gSomeTypeLiftParseJSONTaggedObject gSomeTypeLiftToJSONTaggedObject)
371− , testProperty "ObjectWithSingleField unary" (toParseJSON1 gSomeTypeLiftParseJSONObjectWithSingleField gSomeTypeLiftToJSONObjectWithSingleField)
372−#endif
373− ]
374− ]
375− , testGroup "OneConstructor" [
376− testProperty "default" (isEmptyArray . gOneConstructorToJSONDefault)
377− , testProperty "Tagged" (isTaggedObject . gOneConstructorToJSONTagged)
378− , testGroup "roundTrip" [
379− testProperty "default" (toParseJSON gOneConstructorParseJSONDefault gOneConstructorToJSONDefault)
380− , testProperty "Tagged" (toParseJSON gOneConstructorParseJSONTagged gOneConstructorToJSONTagged)
381− ]
382− ]
383− , testGroup "OptionField" [
384− testProperty "like Maybe" $
385− \x -> gOptionFieldToJSON (OptionField (Option x)) === thMaybeFieldToJSON (MaybeField x)
386− , testProperty "roundTrip" (toParseJSON gOptionFieldParseJSON gOptionFieldToJSON)
387− ]
388− ]
389− , testGroup "toEncoding" [
390− testProperty "NullaryString" $
391− gNullaryToJSONString `sameAs` gNullaryToEncodingString
392− , testProperty "Nullary2ElemArray" $
393− gNullaryToJSON2ElemArray `sameAs` gNullaryToEncoding2ElemArray
394− , testProperty "NullaryTaggedObject" $
395− gNullaryToJSONTaggedObject `sameAs` gNullaryToEncodingTaggedObject
396− , testProperty "NullaryObjectWithSingleField" $
397− gNullaryToJSONObjectWithSingleField `sameAs`
398− gNullaryToEncodingObjectWithSingleField
399− -- , testProperty "ApproxUnwrap" $
400− -- gApproxToJSONUnwrap `sameAs` gApproxToEncodingUnwrap
401− , testProperty "ApproxDefault" $
402− gApproxToJSONDefault `sameAs` gApproxToEncodingDefault
403−
404− , testProperty "EitherTextInt UntaggedValue" $
405− gEitherTextIntToJSONUntaggedValue `sameAs` gEitherTextIntToEncodingUntaggedValue
406−
407− , testProperty "SomeType2ElemArray" $
408− gSomeTypeToJSON2ElemArray `sameAs` gSomeTypeToEncoding2ElemArray
409−#if __GLASGOW_HASKELL__ >= 706
410− , testProperty "SomeType2ElemArray unary" $
411− gSomeTypeLiftToJSON2ElemArray `sameAs1` gSomeTypeLiftToEncoding2ElemArray
412− , testProperty "SomeType2ElemArray unary agree" $
413− gSomeTypeToEncoding2ElemArray `sameAs1Agree` gSomeTypeLiftToEncoding2ElemArray
414−#endif
415−
416− , testProperty "SomeTypeTaggedObject" $
417− gSomeTypeToJSONTaggedObject `sameAs` gSomeTypeToEncodingTaggedObject
418−#if __GLASGOW_HASKELL__ >= 706
419− , testProperty "SomeTypeTaggedObject unary" $
420− gSomeTypeLiftToJSONTaggedObject `sameAs1` gSomeTypeLiftToEncodingTaggedObject
421− , testProperty "SomeTypeTaggedObject unary agree" $
422− gSomeTypeToEncodingTaggedObject `sameAs1Agree` gSomeTypeLiftToEncodingTaggedObject
423−#endif
424−
425− , testProperty "SomeTypeObjectWithSingleField" $
426− gSomeTypeToJSONObjectWithSingleField `sameAs` gSomeTypeToEncodingObjectWithSingleField
427−#if __GLASGOW_HASKELL__ >= 706
428− , testProperty "SomeTypeObjectWithSingleField unary" $
429− gSomeTypeLiftToJSONObjectWithSingleField `sameAs1` gSomeTypeLiftToEncodingObjectWithSingleField
430− , testProperty "SomeTypeObjectWithSingleField unary agree" $
431− gSomeTypeToEncodingObjectWithSingleField `sameAs1Agree` gSomeTypeLiftToEncodingObjectWithSingleField
432−#endif
433−
434− , testProperty "SomeTypeOmitNothingFields" $
435− gSomeTypeToJSONOmitNothingFields `sameAs` gSomeTypeToEncodingOmitNothingFields
436−
437− , testProperty "OneConstructorDefault" $
438− gOneConstructorToJSONDefault `sameAs` gOneConstructorToEncodingDefault
439− , testProperty "OneConstructorTagged" $
440− gOneConstructorToJSONTagged `sameAs` gOneConstructorToEncodingTagged
441−
442− , testProperty "OptionField" $
443− gOptionFieldToJSON `sameAs` gOptionFieldToEncoding
444− ]
445− ]
446− , testGroup "template-haskell" [
447− testGroup "toJSON" [
448− testGroup "Nullary" [
449− testProperty "string" (isString . thNullaryToJSONString)
450− , testProperty "2ElemArray" (is2ElemArray . thNullaryToJSON2ElemArray)
451− , testProperty "TaggedObject" (isNullaryTaggedObject . thNullaryToJSONTaggedObject)
452− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thNullaryToJSONObjectWithSingleField)
453−
454− , testGroup "roundTrip" [
455− testProperty "string" (toParseJSON thNullaryParseJSONString thNullaryToJSONString)
456− , testProperty "2ElemArray" (toParseJSON thNullaryParseJSON2ElemArray thNullaryToJSON2ElemArray)
457− , testProperty "TaggedObject" (toParseJSON thNullaryParseJSONTaggedObject thNullaryToJSONTaggedObject)
458− , testProperty "ObjectWithSingleField" (toParseJSON thNullaryParseJSONObjectWithSingleField thNullaryToJSONObjectWithSingleField)
459− ]
460− ]
461− , testGroup "EitherTextInt" [
462− testProperty "UntaggedValue" (isUntaggedValueETI . thEitherTextIntToJSONUntaggedValue)
463− , testProperty "roundtrip" (toParseJSON thEitherTextIntParseJSONUntaggedValue thEitherTextIntToJSONUntaggedValue)
464− ]
465− , testGroup "SomeType" [
466− testProperty "2ElemArray" (is2ElemArray . thSomeTypeToJSON2ElemArray)
467− , testProperty "TaggedObject" (isTaggedObject . thSomeTypeToJSONTaggedObject)
468− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thSomeTypeToJSONObjectWithSingleField)
469− , testGroup "roundTrip" [
470− testProperty "2ElemArray" (toParseJSON thSomeTypeParseJSON2ElemArray thSomeTypeToJSON2ElemArray)
471− , testProperty "TaggedObject" (toParseJSON thSomeTypeParseJSONTaggedObject thSomeTypeToJSONTaggedObject)
472− , testProperty "ObjectWithSingleField" (toParseJSON thSomeTypeParseJSONObjectWithSingleField thSomeTypeToJSONObjectWithSingleField)
473−
474− , testProperty "2ElemArray unary" (toParseJSON1 thSomeTypeLiftParseJSON2ElemArray thSomeTypeLiftToJSON2ElemArray)
475− , testProperty "TaggedObject unary" (toParseJSON1 thSomeTypeLiftParseJSONTaggedObject thSomeTypeLiftToJSONTaggedObject)
476− , testProperty "ObjectWithSingleField unary" (toParseJSON1 thSomeTypeLiftParseJSONObjectWithSingleField thSomeTypeLiftToJSONObjectWithSingleField)
477−
478− ]
479− ]
480− , testGroup "Approx" [
481− testProperty "string" (isString . thApproxToJSONUnwrap)
482− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thApproxToJSONDefault)
483− , testGroup "roundTrip" [
484− testProperty "string" (toParseJSON thApproxParseJSONUnwrap thApproxToJSONUnwrap)
485− , testProperty "ObjectWithSingleField" (toParseJSON thApproxParseJSONDefault thApproxToJSONDefault)
486− ]
487− ]
488− , testGroup "GADT" [
489− testProperty "string" (isString . thGADTToJSONUnwrap)
490− , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thGADTToJSONDefault)
491− , testGroup "roundTrip" [
492− testProperty "string" (toParseJSON thGADTParseJSONUnwrap thGADTToJSONUnwrap)
493− , testProperty "ObjectWithSingleField" (toParseJSON thGADTParseJSONDefault thGADTToJSONDefault)
494− ]
495− ]
496− , testGroup "OneConstructor" [
497− testProperty "default" (isEmptyArray . thOneConstructorToJSONDefault)
498− , testProperty "Tagged" (isTaggedObject . thOneConstructorToJSONTagged)
499− , testGroup "roundTrip" [
500− testProperty "default" (toParseJSON thOneConstructorParseJSONDefault thOneConstructorToJSONDefault)
501− , testProperty "Tagged" (toParseJSON thOneConstructorParseJSONTagged thOneConstructorToJSONTagged)
502− ]
503− ]
504− , testGroup "OptionField" [
505− testProperty "like Maybe" $
506− \x -> thOptionFieldToJSON (OptionField (Option x)) === thMaybeFieldToJSON (MaybeField x)
507− , testProperty "roundTrip" (toParseJSON thOptionFieldParseJSON thOptionFieldToJSON)
508− ]
509− ]
510− , testGroup "toEncoding" [
511− testProperty "NullaryString" $
512− thNullaryToJSONString `sameAs` thNullaryToEncodingString
513− , testProperty "Nullary2ElemArray" $
514− thNullaryToJSON2ElemArray `sameAs` thNullaryToEncoding2ElemArray
515− , testProperty "NullaryTaggedObject" $
516− thNullaryToJSONTaggedObject `sameAs` thNullaryToEncodingTaggedObject
517− , testProperty "NullaryObjectWithSingleField" $
518− thNullaryToJSONObjectWithSingleField `sameAs`
519− thNullaryToEncodingObjectWithSingleField
520− , testProperty "ApproxUnwrap" $
521− thApproxToJSONUnwrap `sameAs` thApproxToEncodingUnwrap
522− , testProperty "ApproxDefault" $
523− thApproxToJSONDefault `sameAs` thApproxToEncodingDefault
524−
525− , testProperty "EitherTextInt UntaggedValue" $
526− thEitherTextIntToJSONUntaggedValue `sameAs` thEitherTextIntToEncodingUntaggedValue
527−
528− , testProperty "SomeType2ElemArray" $
529− thSomeTypeToJSON2ElemArray `sameAs` thSomeTypeToEncoding2ElemArray
530− , testProperty "SomeType2ElemArray unary" $
531− thSomeTypeLiftToJSON2ElemArray `sameAs1` thSomeTypeLiftToEncoding2ElemArray
532− , testProperty "SomeType2ElemArray unary agree" $
533− thSomeTypeToEncoding2ElemArray `sameAs1Agree` thSomeTypeLiftToEncoding2ElemArray
534−
535− , testProperty "SomeTypeTaggedObject" $
536− thSomeTypeToJSONTaggedObject `sameAs` thSomeTypeToEncodingTaggedObject
537− , testProperty "SomeTypeTaggedObject unary" $
538− thSomeTypeLiftToJSONTaggedObject `sameAs1` thSomeTypeLiftToEncodingTaggedObject
539− , testProperty "SomeTypeTaggedObject unary agree" $
540− thSomeTypeToEncodingTaggedObject `sameAs1Agree` thSomeTypeLiftToEncodingTaggedObject
541−
542− , testProperty "SomeTypeObjectWithSingleField" $
543− thSomeTypeToJSONObjectWithSingleField `sameAs` thSomeTypeToEncodingObjectWithSingleField
544− , testProperty "SomeTypeObjectWithSingleField unary" $
545− thSomeTypeLiftToJSONObjectWithSingleField `sameAs1` thSomeTypeLiftToEncodingObjectWithSingleField
546− , testProperty "SomeTypeObjectWithSingleField unary agree" $
547− thSomeTypeToEncodingObjectWithSingleField `sameAs1Agree` thSomeTypeLiftToEncodingObjectWithSingleField
548−
549− , testProperty "OneConstructorDefault" $
550− thOneConstructorToJSONDefault `sameAs` thOneConstructorToEncodingDefault
551− , testProperty "OneConstructorTagged" $
552− thOneConstructorToJSONTagged `sameAs` thOneConstructorToEncodingTagged
553−
554− , testProperty "OptionField" $
555− thOptionFieldToJSON `sameAs` thOptionFieldToEncoding
556− ]
557− ]
558 ]
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+47−23
@@ -3,9 +3,7 @@
3{-# LANGUAGE GADTs #-}
3{-# LANGUAGE GADTs #-}
4{-# LANGUAGE NoImplicitPrelude #-}
4{-# LANGUAGE NoImplicitPrelude #-}
5{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE OverloadedStrings #-}
6−#if __GLASGOW_HASKELL__ >= 708
7{-# LANGUAGE DataKinds #-}
6{-# LANGUAGE DataKinds #-}
8−#endif
9
7
10------------------------------------------------------------------------------
8------------------------------------------------------------------------------
11-- 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.
@@ -32,6 +30,9 @@ 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 ()
@@ -68,13 +69,14 @@ jsonExamples =
68 example "Either Left" "{\"Left\":1}" (Left 1 :: Either Int Int)
69 example "Either Left" "{\"Left\":1}" (Left 1 :: Either Int Int)
69 , example "Either Right" "{\"Right\":1}" (Right 1 :: Either Int Int)
70 , example "Either Right" "{\"Right\":1}" (Right 1 :: Either Int Int)
70 , 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
71 , example "Just" "1" (Just 1 :: Maybe Int)
75 , example "Just" "1" (Just 1 :: Maybe Int)
72 , example "Proxy Int" "null" (Proxy :: Proxy Int)
76 , example "Proxy Int" "null" (Proxy :: Proxy Int)
73 , example "Tagged Char Int" "1" (Tagged 1 :: Tagged Char Int)
77 , example "Tagged Char Int" "1" (Tagged 1 :: Tagged Char Int)
74−#if __GLASGOW_HASKELL__ >= 708
75 -- Test Tagged instance is polykinded
78 -- Test Tagged instance is polykinded
76 , example "Tagged 123 Int" "1" (Tagged 1 :: Tagged 123 Int)
79 , example "Tagged 123 Int" "1" (Tagged 1 :: Tagged 123 Int)
77−#endif
78 , example "Const Char Int" "\"c\"" (Const 'c' :: Const Char Int)
80 , example "Const Char Int" "\"c\"" (Const 'c' :: Const Char Int)
79 , example "Tuple" "[1,2]" ((1, 2) :: (Int, Int))
81 , example "Tuple" "[1,2]" ((1, 2) :: (Int, Int))
80 , example "NonEmpty" "[1,2,3]" (1 :| [2, 3] :: NonEmpty Int)
82 , example "NonEmpty" "[1,2,3]" (1 :| [2, 3] :: NonEmpty Int)
@@ -82,22 +84,22 @@ jsonExamples =
82 , 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)
83 , example "()" "[]" ()
85 , example "()" "[]" ()
84
86
85− , Example "HashMap Int Int"
87+ , ndExample "HashMap Int Int"
86 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
88 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
87 (HM.fromList [(0,1),(2,3)] :: HM.HashMap Int Int)
89 (HM.fromList [(0,1),(2,3)] :: HM.HashMap Int Int)
88− , Example "Map Int Int"
90+ , ndExample "Map Int Int"
89 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
91 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
90 (M.fromList [(0,1),(2,3)] :: M.Map Int Int)
92 (M.fromList [(0,1),(2,3)] :: M.Map Int Int)
91− , Example "Map (Tagged Int Int) Int"
93+ , ndExample "Map (Tagged Int Int) Int"
92 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
94 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
93 (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)
94 , example "Map [Int] Int"
96 , example "Map [Int] Int"
95 "[[[0],1],[[2],3]]"
97 "[[[0],1],[[2],3]]"
96 (M.fromList [([0],1),([2],3)] :: M.Map [Int] Int)
98 (M.fromList [([0],1),([2],3)] :: M.Map [Int] Int)
97− , Example "Map [Char] Int"
99+ , ndExample "Map [Char] Int"
98 [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]
100 [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]
99 (M.fromList [("ab",1),("cd",3)] :: M.Map String Int)
101 (M.fromList [("ab",1),("cd",3)] :: M.Map String Int)
100− , Example "Map [I Char] Int"
102+ , ndExample "Map [I Char] Int"
101 [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]
103 [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]
102 (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)
103
105
@@ -193,16 +195,27 @@ jsonExamples =
193 , example "Semigroup.WrappedMonoid Int" "2" (Semigroup.WrapMonoid 2 :: Semigroup.WrappedMonoid Int)
195 , example "Semigroup.WrappedMonoid Int" "2" (Semigroup.WrapMonoid 2 :: Semigroup.WrappedMonoid Int)
194 , example "Semigroup.Option Just" "2" (pure 2 :: Semigroup.Option Int)
196 , example "Semigroup.Option Just" "2" (pure 2 :: Semigroup.Option Int)
195 , 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
196 ]
211 ]
197
212
198jsonEncodingExamples :: [Example]
213jsonEncodingExamples :: [Example]
199jsonEncodingExamples =
214jsonEncodingExamples =
200 [
215 [
201− -- Maybe serialising is lossy
202− -- https://github.com/bos/aeson/issues/376
203− example "Just Nothing" "null" (Just Nothing :: Maybe (Maybe Int))
204 -- 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)
205− , example "inf :: Double" "null" (Approx $ 1/0 :: Approx Double)
218+ , example "-inf :: Double" "null" (Approx $ -1/0 :: Approx Double)
206 ]
219 ]
207
220
208jsonDecodingExamples :: [Example]
221jsonDecodingExamples :: [Example]
@@ -224,16 +237,27 @@ jsonDecodingExamples = [
224 ]
237 ]
225
238
226data Example where
239data Example where
227− Example
240+ Example
228− :: (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
229− => String -> [L.ByteString] -> a -> Example -- empty bytestring will fail, any p [] == False
246+ -> Example -- empty bytestring will fail, any p [] == False
247+
230− MaybeExample
248+ MaybeExample
231− :: (Eq a, Show a, FromJSON a)
249+ :: (Eq a, Show a, FromJSON a)
232− => String -> L.ByteString -> Maybe a -> Example
250+ => String -> L.ByteString -> Maybe a -> Example
233
251
234example :: (Eq a, Show a, ToJSON a, FromJSON a)
252example :: (Eq a, Show a, ToJSON a, FromJSON a)
235 => String -> L.ByteString -> a -> Example
253 => String -> L.ByteString -> a -> Example
236−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+
237
261
238data MyEither a b = MyLeft a | MyRight b
262data MyEither a b = MyLeft a | MyRight b
239 deriving (Generic, Show, Eq)
263 deriving (Generic, Show, Eq)
@@ -246,16 +270,16 @@ instance (FromJSON a, FromJSON b) => FromJSON (MyEither a b) where
246 parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }
270 parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }
247
271
248assertJsonExample :: Example -> TestTree
272assertJsonExample :: Example -> TestTree
249−assertJsonExample (Example name bss val) = testCase name $ do
273+assertJsonExample (Example name bss val val') = testCase name $ do
250 assertSomeEqual "encode" bss (encode val)
274 assertSomeEqual "encode" bss (encode val)
251 assertSomeEqual "encode/via value" bss (encode $ toJSON val)
275 assertSomeEqual "encode/via value" bss (encode $ toJSON val)
252 for_ bss $ \bs ->
276 for_ bss $ \bs ->
253− assertEqual "decode" (Just val) (decode bs)
277+ assertEqual "decode" (Just val') (decode bs)
254assertJsonExample (MaybeExample name bs mval) = testCase name $
278assertJsonExample (MaybeExample name bs mval) = testCase name $
255 assertEqual "decode" mval (decode bs)
279 assertEqual "decode" mval (decode bs)
256
280
257assertJsonEncodingExample :: Example -> TestTree
281assertJsonEncodingExample :: Example -> TestTree
258−assertJsonEncodingExample (Example name bss val) = testCase name $ do
282+assertJsonEncodingExample (Example name bss val _) = testCase name $ do
259 assertSomeEqual "encode" bss (encode val)
283 assertSomeEqual "encode" bss (encode val)
260 assertSomeEqual "encode/via value" bss (encode $ toJSON val)
284 assertSomeEqual "encode/via value" bss (encode $ toJSON val)
261assertJsonEncodingExample (MaybeExample name _ _) = testCase name $
285assertJsonEncodingExample (MaybeExample name _ _) = testCase name $
tests/Types.hs−2
@@ -119,9 +119,7 @@ deriving instance Generic (Sum4 a b c d)
119deriving instance Generic (Approx a)
119deriving instance Generic (Approx a)
120deriving instance Generic Nullary
120deriving instance Generic Nullary
121deriving instance Generic (SomeType a)
121deriving instance Generic (SomeType a)
122−#if __GLASGOW_HASKELL__ >= 706
123deriving instance Generic1 SomeType
122deriving instance Generic1 SomeType
124−#endif
125deriving instance Generic OptionField
123deriving instance Generic OptionField
126deriving instance Generic EitherTextInt
124deriving instance Generic EitherTextInt
127
125
tests/UnitTests.hs+197−34
@@ -5,6 +5,7 @@
5{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE OverloadedStrings #-}
6{-# LANGUAGE ScopedTypeVariables #-}
6{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE TemplateHaskell #-}
7{-# LANGUAGE TemplateHaskell #-}
8+{-# LANGUAGE QuasiQuotes #-}
8
9
9-- For Data.Aeson.Types.camelTo
10-- For Data.Aeson.Types.camelTo
10{-# OPTIONS_GHC -fno-warn-deprecations #-}
11{-# OPTIONS_GHC -fno-warn-deprecations #-}
@@ -25,38 +26,47 @@ import Prelude.Compat
25import Control.Monad (forM, forM_)
26import Control.Monad (forM, forM_)
26import Data.Aeson ((.=), (.:), (.:?), (.:!), FromJSON(..), FromJSONKeyFunction(..), FromJSONKey(..), ToJSON1(..), decode, eitherDecode, encode, fromJSON, genericParseJSON, genericToEncoding, genericToJSON, object, withObject, withEmbeddedJSON)
27import Data.Aeson ((.=), (.:), (.:?), (.:!), FromJSON(..), FromJSONKeyFunction(..), FromJSONKey(..), ToJSON1(..), decode, eitherDecode, encode, fromJSON, genericParseJSON, genericToEncoding, genericToJSON, object, withObject, withEmbeddedJSON)
27import Data.Aeson.Internal (JSONPathElement(..), formatError)
28import Data.Aeson.Internal (JSONPathElement(..), formatError)
29+import Data.Aeson.QQ.Simple (aesonQQ)
28import Data.Aeson.TH (deriveJSON, deriveToJSON, deriveToJSON1)
30import Data.Aeson.TH (deriveJSON, deriveToJSON, deriveToJSON1)
29import 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
30−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)
31import Data.Char (toUpper)
40import Data.Char (toUpper)
32import Data.Either.Compat (isLeft, isRight)
41import Data.Either.Compat (isLeft, isRight)
33import Data.Hashable (hash)
42import Data.Hashable (hash)
34import Data.HashMap.Strict (HashMap)
43import Data.HashMap.Strict (HashMap)
35−import Data.List (sort)
44+import Data.List (sort, isSuffixOf)
36import Data.Maybe (fromMaybe)
45import Data.Maybe (fromMaybe)
37−import Data.Sequence (Seq)
38import Data.Scientific (Scientific, scientific)
46import Data.Scientific (Scientific, scientific)
39import Data.Tagged (Tagged(..))
47import Data.Tagged (Tagged(..))
40import Data.Text (Text)
48import Data.Text (Text)
41import Data.Time (UTCTime)
49import Data.Time (UTCTime)
42−import Data.Time.Format (parseTime)
50+import Data.Time.Format.Compat (parseTimeM, defaultTimeLocale)
43−import Data.Time.Locale.Compat (defaultTimeLocale)
44import GHC.Generics (Generic)
51import GHC.Generics (Generic)
45import Instances ()
52import Instances ()
46import Numeric.Natural (Natural)
53import Numeric.Natural (Natural)
47import System.Directory (getDirectoryContents)
54import System.Directory (getDirectoryContents)
48import System.FilePath ((</>), takeExtension, takeFileName)
55import System.FilePath ((</>), takeExtension, takeFileName)
49import Test.Tasty (TestTree, testGroup)
56import Test.Tasty (TestTree, testGroup)
50−import Test.Tasty.HUnit (Assertion, assertBool, assertFailure, assertEqual, testCase)
57+import Test.Tasty.HUnit (Assertion, assertBool, assertFailure, assertEqual, testCase, (@?=))
51import Text.Printf (printf)
58import Text.Printf (printf)
52import UnitTests.NullaryConstructors (nullaryConstructors)
59import UnitTests.NullaryConstructors (nullaryConstructors)
60+import qualified Data.ByteString as S
53import qualified Data.ByteString.Base16.Lazy as LBase16
61import qualified Data.ByteString.Base16.Lazy as LBase16
54import qualified Data.ByteString.Lazy.Char8 as L
62import qualified Data.ByteString.Lazy.Char8 as L
55import qualified Data.HashSet as HashSet
63import qualified Data.HashSet as HashSet
64+import qualified Data.HashMap.Lazy as HashMap
56import qualified Data.Text.Lazy as LT
65import qualified Data.Text.Lazy as LT
57import qualified Data.Text.Lazy.Builder as TLB
66import qualified Data.Text.Lazy.Builder as TLB
58import qualified Data.Text.Lazy.Encoding as LT
67import qualified Data.Text.Lazy.Encoding as LT
59import qualified Data.Text.Lazy.Encoding as TLE
68import qualified Data.Text.Lazy.Encoding as TLE
69+import qualified Data.Vector as Vector
60import qualified ErrorMessages
70import qualified ErrorMessages
61import qualified SerializationFormatSpec
71import qualified SerializationFormatSpec
62
72
@@ -91,8 +101,8 @@ tests = testGroup "unit" [
91 testCase "example 1" formatErrorExample
101 testCase "example 1" formatErrorExample
92 ]
102 ]
93 , testGroup ".:, .:?, .:!" $ fmap (testCase "-") dotColonMark
103 , testGroup ".:, .:?, .:!" $ fmap (testCase "-") dotColonMark
94− , testGroup "JSONPath" $ fmap (testCase "-") jsonPath
95 , testGroup "Hashable laws" $ fmap (testCase "-") hashableLaws
104 , testGroup "Hashable laws" $ fmap (testCase "-") hashableLaws
105+ , testGroup "Object construction" $ fmap (testCase "-") objectConstruction
96 , testGroup "Issue #351" $ fmap (testCase "-") issue351
106 , testGroup "Issue #351" $ fmap (testCase "-") issue351
97 , testGroup "Nullary constructors" $ fmap (testCase "-") nullaryConstructors
107 , testGroup "Nullary constructors" $ fmap (testCase "-") nullaryConstructors
98 , testGroup "FromJSONKey" $ fmap (testCase "-") fromJSONKeyAssertions
108 , testGroup "FromJSONKey" $ fmap (testCase "-") fromJSONKeyAssertions
@@ -102,11 +112,20 @@ tests = testGroup "unit" [
102 , testGroup "SingleMaybeField" singleMaybeField
112 , testGroup "SingleMaybeField" singleMaybeField
103 , testCase "withEmbeddedJSON" withEmbeddedJSONTest
113 , testCase "withEmbeddedJSON" withEmbeddedJSONTest
104 , testCase "SingleFieldCon" singleFieldCon
114 , testCase "SingleFieldCon" singleFieldCon
115+ , testGroup "UnknownFields" unknownFields
116+ , testGroup "Ordering of object keys" keyOrdering
105 , testCase "Ratio with denominator 0" ratioDenominator0
117 , testCase "Ratio with denominator 0" ratioDenominator0
118+ , testCase "Rational parses number" rationalNumber
119+ , testCase "Big rational" bigRationalDecoding
120+ , testCase "Small rational" smallRationalDecoding
106 , testCase "Big scientific exponent" bigScientificExponent
121 , testCase "Big scientific exponent" bigScientificExponent
107 , testCase "Big integer decoding" bigIntegerDecoding
122 , testCase "Big integer decoding" bigIntegerDecoding
108 , testCase "Big natural decading" bigNaturalDecoding
123 , testCase "Big natural decading" bigNaturalDecoding
109 , testCase "Big integer key decoding" bigIntegerKeyDecoding
124 , testCase "Big integer key decoding" bigIntegerKeyDecoding
125+ , testGroup "QQ.Simple"
126+ [ testCase "example" $
127+ assertEqual "" (object ["foo" .= True]) [aesonQQ| {"foo": true } |]
128+ ]
110 ]
129 ]
111
130
112roundTripCamel :: String -> Assertion
131roundTripCamel :: String -> Assertion
@@ -199,10 +218,18 @@ utcTimeGood = do
199 assertEqual "utctime" (parseWithRead "%F %H:%MZ" ts11) t11
218 assertEqual "utctime" (parseWithRead "%F %H:%MZ" ts11) t11
200 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
201
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+
202 where
229 where
203 parseWithRead :: String -> LT.Text -> UTCTime
230 parseWithRead :: String -> LT.Text -> UTCTime
204 parseWithRead f s =
231 parseWithRead f s =
205− fromMaybe (error "parseTime input malformed") . parseTime defaultTimeLocale f . LT.unpack $ s
232+ fromMaybe (error "parseTime input malformed") . parseTimeM True defaultTimeLocale f . LT.unpack $ s
206 parseWithAeson :: LT.Text -> Maybe UTCTime
233 parseWithAeson :: LT.Text -> Maybe UTCTime
207 parseWithAeson s = decode . LT.encodeUtf8 $ LT.concat ["\"", s, "\""]
234 parseWithAeson s = decode . LT.encodeUtf8 $ LT.concat ["\"", s, "\""]
208
235
@@ -218,6 +245,7 @@ utcTimeBad = do
218 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
219 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
220 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
221 where
249 where
222 verifyFailParse (s :: LT.Text) =
250 verifyFailParse (s :: LT.Text) =
223 let (dec :: Maybe UTCTime) = decode . LT.encodeUtf8 $ LT.concat ["\"", s, "\""] in
251 let (dec :: Maybe UTCTime) = decode . LT.encodeUtf8 $ LT.concat ["\"", s, "\""] in
@@ -230,6 +258,18 @@ formatErrorExample =
230 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"
231 in assertEqual "formatError example" lhs rhs
259 in assertEqual "formatError example" lhs rhs
232
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+
233------------------------------------------------------------------------------
273------------------------------------------------------------------------------
234-- Comparison (.:?) and (.:!)
274-- Comparison (.:?) and (.:!)
235------------------------------------------------------------------------------
275------------------------------------------------------------------------------
@@ -260,27 +300,6 @@ dotColonMark = [
260 ex2 = "{\"value\": 42 }"
300 ex2 = "{\"value\": 42 }"
261 ex3 = "{\"value\": null }"
301 ex3 = "{\"value\": null }"
262
302
263−------------------------------------------------------------------------------
264−-- These tests check that JSONPath is tracked correctly
265−-----------------------------------------------------------------------------
266−
267−jsonPath :: [Assertion]
268−jsonPath = [
269− -- issue #356
270− assertEqual "Either"
271− (Left "Error in $[1].Left[1]: expected Bool, encountered Number")
272− (eitherDecode "[1,{\"Left\":[2,3]}]"
273− :: Either String (Int, Either (Int, Bool) ()))
274− -- issue #358
275− , assertEqual "Seq a"
276− (Left "Error in $[2]: expected Int, encountered Boolean")
277− (eitherDecode "[0,1,true]" :: Either String (Seq Int))
278− , assertEqual "Wibble"
279− (Left "Error in $.wibbleInt: expected Int, encountered Boolean")
280− (eitherDecode "{\"wibbleString\":\"\",\"wibbleInt\":true}"
281− :: Either String Wibble)
282− ]
283−
284------------------------------------------------------------------------------
303------------------------------------------------------------------------------
285-- Check that the hashes of two equal Value are the same
304-- Check that the hashes of two equal Value are the same
286------------------------------------------------------------------------------
305------------------------------------------------------------------------------
@@ -293,6 +312,18 @@ hashableLaws = [
293 a = object ["223" .= False, "807882556" .= True]
312 a = object ["223" .= False, "807882556" .= True]
294 b = object ["807882556" .= True, "223" .= False]
313 b = object ["807882556" .= True, "223" .= False]
295
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+
296-------------------------------------------------------------------------------
327-------------------------------------------------------------------------------
297-- ToJSONKey
328-- ToJSONKey
298-------------------------------------------------------------------------------
329-------------------------------------------------------------------------------
@@ -509,6 +540,7 @@ showOptions =
509 ++ ", sumEncoding = TaggedObject {tagFieldName = \"tag\", contentsFieldName = \"contents\"}"
540 ++ ", sumEncoding = TaggedObject {tagFieldName = \"tag\", contentsFieldName = \"contents\"}"
510 ++ ", unwrapUnaryRecords = False"
541 ++ ", unwrapUnaryRecords = False"
511 ++ ", tagSingleConstructors = False"
542 ++ ", tagSingleConstructors = False"
543+ ++ ", rejectUnknownFields = False"
512 ++ "}")
544 ++ "}")
513 (show defaultOptions)
545 (show defaultOptions)
514
546
@@ -553,12 +585,138 @@ singleFieldCon :: Assertion
553singleFieldCon =
585singleFieldCon =
554 assertEqual "fromJSON" (Right (SingleFieldCon 0)) (eitherDecode "0")
586 assertEqual "fromJSON" (Right (SingleFieldCon 0)) (eitherDecode "0")
555
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+
556ratioDenominator0 :: Assertion
695ratioDenominator0 :: Assertion
557ratioDenominator0 =
696ratioDenominator0 =
558 assertEqual "Ratio with denominator 0"
697 assertEqual "Ratio with denominator 0"
559 (Left "Error in $: Ratio denominator was 0")
698 (Left "Error in $: Ratio denominator was 0")
560 (eitherDecode "{ \"numerator\": 1, \"denominator\": 0 }" :: Either String Rational)
699 (eitherDecode "{ \"numerator\": 1, \"denominator\": 0 }" :: Either String Rational)
561
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+
562bigScientificExponent :: Assertion
720bigScientificExponent :: Assertion
563bigScientificExponent =
721bigScientificExponent =
564 assertEqual "Encoding an integral scientific with a large exponent should normalize it"
722 assertEqual "Encoding an integral scientific with a large exponent should normalize it"
@@ -568,25 +726,25 @@ bigScientificExponent =
568bigIntegerDecoding :: Assertion
726bigIntegerDecoding :: Assertion
569bigIntegerDecoding =
727bigIntegerDecoding =
570 assertEqual "Decoding an Integer with a large exponent should fail"
728 assertEqual "Decoding an Integer with a large exponent should fail"
571− (Left "Error in $: expected a number with exponent <= 1024, encountered Number")
729+ (Left "Error in $: parsing Integer failed, found a number with exponent 2000, but it must not be greater than 1024")
572 ((eitherDecode :: L.ByteString -> Either String Integer) "1e2000")
730 ((eitherDecode :: L.ByteString -> Either String Integer) "1e2000")
573
731
574bigNaturalDecoding :: Assertion
732bigNaturalDecoding :: Assertion
575bigNaturalDecoding =
733bigNaturalDecoding =
576 assertEqual "Decoding a Natural with a large exponent should fail"
734 assertEqual "Decoding a Natural with a large exponent should fail"
577− (Left "Error in $: expected a number with exponent <= 1024, encountered Number")
735+ (Left "Error in $: parsing Natural failed, found a number with exponent 2000, but it must not be greater than 1024")
578− ((eitherDecode :: L.ByteString -> Either String Integer) "1e2000")
736+ ((eitherDecode :: L.ByteString -> Either String Natural) "1e2000")
579
737
580bigIntegerKeyDecoding :: Assertion
738bigIntegerKeyDecoding :: Assertion
581bigIntegerKeyDecoding =
739bigIntegerKeyDecoding =
582 assertEqual "Decoding an Integer key with a large exponent should fail"
740 assertEqual "Decoding an Integer key with a large exponent should fail"
583− (Left "Error in $['1e2000']: expected a number with exponent <= 1024, encountered Number")
741+ (Left "Error in $['1e2000']: parsing Integer failed, found a number with exponent 2000, but it must not be greater than 1024")
584 ((eitherDecode :: L.ByteString -> Either String (HashMap Integer Value)) "{ \"1e2000\": null }")
742 ((eitherDecode :: L.ByteString -> Either String (HashMap Integer Value)) "{ \"1e2000\": null }")
585
743
586bigNaturalKeyDecoding :: Assertion
744bigNaturalKeyDecoding :: Assertion
587bigNaturalKeyDecoding =
745bigNaturalKeyDecoding =
588 assertEqual "Decoding an Integer key with a large exponent should fail"
746 assertEqual "Decoding an Integer key with a large exponent should fail"
589− (Left "Error in $['1e2000']: expected a number with exponent <= 1024, encountered Number")
747+ (Left "Error in $['1e2000']: found a number with exponent 2000, but it must not be greater than 1024")
590 ((eitherDecode :: L.ByteString -> Either String (HashMap Natural Value)) "{ \"1e2000\": null }")
748 ((eitherDecode :: L.ByteString -> Either String (HashMap Natural Value)) "{ \"1e2000\": null }")
591
749
592deriveJSON defaultOptions{omitNothingFields=True} ''MyRecord
750deriveJSON defaultOptions{omitNothingFields=True} ''MyRecord
@@ -595,3 +753,8 @@ deriveToJSON defaultOptions ''Foo
595deriveToJSON1 defaultOptions ''Foo
753deriveToJSON1 defaultOptions ''Foo
596
754
597deriveJSON 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+5
@@ -14,6 +14,7 @@ import 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
19import Test.Tasty.HUnit ((@=?), Assertion)
20import Test.Tasty.HUnit ((@=?), Assertion)
@@ -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-nightly.yaml --system-ghc --no-terminal
24− stack install hlint --stack-yaml stack-nightly.yaml --system-ghc --no-terminal
25 make lint
27 make lint
26 ;;
28 ;;
27esac
29esac