git.iko.soy/typeable/openapi3
on master help sign in
Merge pull request #34 from biocad/maksbotan/aeson-2

Support aeson-2

commite1ea5fedae3d411f0e413d54bf1f8dfa44bc1a94 authorMaxim Koltsov <kolmax94@gmail.com> dateMon 13 Dec 2021, 10:59 (5 years ago) parentb71892d5b5369d
11 files changed+182−66
.github/workflows/haskell-ci.yml+12−3 cabal.haskell-ci+7 openapi3.cabal+4−2 src/Data/OpenApi/Aeson/Compat.hs+76 src/Data/OpenApi/Internal.hs+14−14 src/Data/OpenApi/Internal/AesonUtils.hs+11−10 src/Data/OpenApi/Internal/Schema.hs+15−14 src/Data/OpenApi/Internal/Schema/Validation.hs+23−11 src/Data/OpenApi/Schema/Generator.hs+3−1 test/Data/OpenApi/Schema/ValidationSpec.hs+17−3 test/SpecCommon.hs−8
.github/workflows/haskell-ci.yml+12−3
@@ -8,9 +8,9 @@
88#
99# For more information, see https://github.com/haskell-CI/haskell-ci
1010#
11−# version: 0.12
11+# version: 0.12.1
1212#
13−# REGENDATA ("0.12",["github","cabal.project"])
13+# REGENDATA ("0.12.1",["github","cabal.project"])
1414#
1515name: Haskell-CI
1616on:
@@ -21,7 +21,7 @@ jobs:
2121 name: Haskell-CI - Linux - ${{ matrix.compiler }}
2222 runs-on: ubuntu-18.04
2323 container:
24− image: buildpack-deps:bionic
24+ image: buildpack-deps:xenial
2525 continue-on-error: ${{ matrix.allow-failure }}
2626 strategy:
2727 matrix:
@@ -175,3 +175,12 @@ jobs:
175175 run: |
176176 rm -f cabal.project.local
177177 $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
178+ - name: prepare for constraint sets
179+ run: |
180+ rm -f cabal.project.local
181+ - name: constraint set aeson-2
182+ run: |
183+ $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --constraint='aeson >=2.0' all
184+ - name: constraint set aeson-1
185+ run: |
186+ $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --constraint='aeson <2.0' all
cabal.haskell-cinew file+7
@@ -0,0 +1,7 @@
1+branches: master
2+
3+constraint-set aeson-1
4+ constraints: aeson <2.0
5+
6+constraint-set aeson-2
7+ constraints: aeson >=2.0
openapi3.cabal+4−2
@@ -56,6 +56,8 @@ library
5656 Data.OpenApi.Internal.AesonUtils
5757 Data.OpenApi.Internal.TypeShape
5858
59+ Data.OpenApi.Aeson.Compat
60+
5961 -- GHC boot libraries
6062 build-depends:
6163 base >=4.11.1.0 && <4.16
@@ -71,8 +73,8 @@ library
7173
7274 -- other dependencies
7375 build-depends:
74− base-compat-batteries >=0.11.1 && <0.12
75− , aeson >=1.4.2.0 && <1.6
76+ base-compat-batteries >=0.11.1 && <0.13
77+ , aeson >=1.4.2.0 && <1.6 || >=2.0.1.0 && < 2.1
7678 , aeson-pretty >=0.8.7 && <0.9
7779 -- cookie 0.4.3 is needed by GHC 7.8 due to time>=1.4 constraint
7880 , cookie >=0.4.3 && <0.5
src/Data/OpenApi/Aeson/Compat.hsnew file+76
@@ -0,0 +1,76 @@
1+{-# LANGUAGE CPP #-}
2+
3+module Data.OpenApi.Aeson.Compat where
4+
5+#if MIN_VERSION_aeson(2,0,0)
6+import Data.Aeson (Key)
7+import qualified Data.Aeson.Key as Key
8+import qualified Data.Aeson.KeyMap as KeyMap
9+#else
10+import qualified Data.HashMap.Strict as HM
11+#endif
12+import Data.Bifunctor (first)
13+import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
14+import qualified Data.Text as T
15+
16+#if MIN_VERSION_aeson(2,0,0)
17+deleteKey :: Key -> KeyMap.KeyMap v -> KeyMap.KeyMap v
18+deleteKey = KeyMap.delete
19+
20+objectToList :: KeyMap.KeyMap v -> [(Key, v)]
21+objectToList = KeyMap.toList
22+
23+objectKeys :: KeyMap.KeyMap v -> [T.Text]
24+objectKeys = map Key.toText . KeyMap.keys
25+
26+stringToKey :: String -> Key
27+stringToKey = Key.fromString
28+
29+keyToString :: Key -> String
30+keyToString = Key.toString
31+
32+keyToText :: Key -> T.Text
33+keyToText = Key.toText
34+
35+toInsOrdHashMap :: KeyMap.KeyMap v -> InsOrdHashMap.InsOrdHashMap T.Text v
36+toInsOrdHashMap = InsOrdHashMap.fromList . fmap (first Key.toText) . KeyMap.toList
37+
38+fromInsOrdHashMap :: InsOrdHashMap.InsOrdHashMap T.Text v -> KeyMap.KeyMap v
39+fromInsOrdHashMap = KeyMap.fromList . fmap (first Key.fromText) . InsOrdHashMap.toList
40+
41+lookupKey :: T.Text -> KeyMap.KeyMap v -> Maybe v
42+lookupKey = KeyMap.lookup . Key.fromText
43+
44+hasKey :: T.Text -> KeyMap.KeyMap a -> Bool
45+hasKey = KeyMap.member . Key.fromText
46+#else
47+deleteKey :: T.Text -> HM.HashMap T.Text v -> HM.HashMap T.Text v
48+deleteKey = HM.delete
49+
50+objectToList :: HM.HashMap T.Text v -> [(T.Text, v)]
51+objectToList = HM.toList
52+
53+objectKeys :: HM.HashMap T.Text v -> [T.Text]
54+objectKeys = HM.keys
55+
56+stringToKey :: String -> T.Text
57+stringToKey = T.pack
58+
59+keyToString :: T.Text -> String
60+keyToString = T.unpack
61+
62+keyToText :: T.Text -> T.Text
63+keyToText = id
64+
65+toInsOrdHashMap :: HM.HashMap T.Text v -> InsOrdHashMap.InsOrdHashMap T.Text v
66+toInsOrdHashMap = InsOrdHashMap.fromHashMap
67+
68+fromInsOrdHashMap :: InsOrdHashMap.InsOrdHashMap T.Text v -> HM.HashMap T.Text v
69+fromInsOrdHashMap = InsOrdHashMap.toHashMap
70+
71+lookupKey :: T.Text -> HM.HashMap T.Text v -> Maybe v
72+lookupKey = HM.lookup
73+
74+hasKey :: T.Text -> HM.HashMap T.Text a -> Bool
75+hasKey = HM.member
76+#endif
src/Data/OpenApi/Internal.hs+14−14
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE DataKinds #-}
23{-# LANGUAGE DeriveDataTypeable #-}
34{-# LANGUAGE DeriveGeneric #-}
@@ -21,6 +22,9 @@ import Prelude.Compat
2122import Control.Applicative
2223import Control.Lens ((&), (.~), (?~))
2324import Data.Aeson hiding (Encoding)
25+#if MIN_VERSION_aeson(2,0,0)
26+import qualified Data.Aeson.KeyMap as KeyMap
27+#endif
2428import qualified Data.Aeson.Types as JSON
2529import Data.Data (Constr, Data (..), DataType, Fixity (..), Typeable,
2630 constrIndex, mkConstr, mkDataType)
@@ -45,17 +49,13 @@ import Text.Read (readMaybe)
4549import Data.HashMap.Strict.InsOrd (InsOrdHashMap)
4650import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
4751
48−import Generics.SOP.TH (deriveGeneric)
49−import Data.OpenApi.Internal.AesonUtils (sopSwaggerGenericToEncoding
50− ,sopSwaggerGenericToJSON
51− ,sopSwaggerGenericToJSONWithOpts
52− ,sopSwaggerGenericParseJSON
53− ,HasSwaggerAesonOptions(..)
54− ,AesonDefaultValue(..)
55− ,mkSwaggerAesonOptions
56− ,saoAdditionalPairs
57− ,saoSubObject)
52+import Data.OpenApi.Aeson.Compat (deleteKey)
53+import Data.OpenApi.Internal.AesonUtils (AesonDefaultValue (..), HasSwaggerAesonOptions (..),
54+ mkSwaggerAesonOptions, saoAdditionalPairs, saoSubObject,
55+ sopSwaggerGenericParseJSON, sopSwaggerGenericToEncoding,
56+ sopSwaggerGenericToJSON, sopSwaggerGenericToJSONWithOpts)
5857import Data.OpenApi.Internal.Utils
58+import Generics.SOP.TH (deriveGeneric)
5959
6060-- $setup
6161-- >>> :seti -XDataKinds
@@ -337,7 +337,7 @@ data RequestBody = RequestBody
337337 -- | The content of the request body.
338338 -- The key is a media type or media type range and the value describes it.
339339 -- For requests that match multiple keys, only the most specific key is applicable.
340− -- e.g. @text/plain@ overrides @text/*@
340+ -- e.g. @text/plain@ overrides @text/\*@
341341 , _requestBodyContent :: InsOrdHashMap MediaType MediaTypeObject
342342
343343 -- | Determines if the request body is required in the request.
@@ -394,7 +394,7 @@ data Encoding = Encoding
394394 -- for other primitive types – @text/plain@; for object - @application/json@;
395395 -- for array – the default is defined based on the inner type.
396396 -- The value can be a specific media type (e.g. @application/json@),
397− -- a wildcard media type (e.g. @image/*@), or a comma-separated list of the two types.
397+ -- a wildcard media type (e.g. @image/\*@), or a comma-separated list of the two types.
398398 _encodingContentType :: Maybe MediaType
399399
400400 -- | A map allowing additional information to be provided as headers,
@@ -734,7 +734,7 @@ data Response = Response
734734 -- | A map containing descriptions of potential response payloads.
735735 -- The key is a media type or media type range and the value describes it.
736736 -- For responses that match multiple keys, only the most specific key is applicable.
737− -- e.g. @text/plain@ overrides @text/*@.
737+ -- e.g. @text/plain@ overrides @text/\*@.
738738 , _responseContent :: InsOrdHashMap MediaType MediaTypeObject
739739
740740 -- | Maps a header name to its definition.
@@ -1492,7 +1492,7 @@ instance FromJSON Param where
14921492instance FromJSON Responses where
14931493 parseJSON (Object o) = Responses
14941494 <$> o .:? "default"
1495− <*> parseJSON (Object (HashMap.delete "default" o))
1495+ <*> parseJSON (Object (deleteKey "default" o))
14961496 parseJSON _ = empty
14971497
14981498instance FromJSON Example where
src/Data/OpenApi/Internal/AesonUtils.hs+11−10
@@ -36,18 +36,19 @@ import Data.Text (Text)
3636import Generics.SOP
3737
3838import qualified Data.Text as T
39−import qualified Data.HashMap.Strict as HM
4039import qualified Data.Set as Set
4140import qualified Data.HashMap.Strict.InsOrd as InsOrd
4241import qualified Data.HashSet.InsOrd as InsOrdHS
4342
43+import Data.OpenApi.Aeson.Compat (keyToString, objectToList, stringToKey)
44+
4445-------------------------------------------------------------------------------
4546-- SwaggerAesonOptions
4647-------------------------------------------------------------------------------
4748
4849data SwaggerAesonOptions = SwaggerAesonOptions
4950 { _saoPrefix :: String
50− , _saoAdditionalPairs :: [(Text, Value)]
51+ , _saoAdditionalPairs :: [Pair]
5152 , _saoSubObject :: Maybe String
5253 }
5354
@@ -154,14 +155,14 @@ sopSwaggerGenericToJSON'' (SwaggerAesonOptions prefix _ sub) = go
154155 go Nil Nil Nil = []
155156 go (I x :* xs) (FieldInfo name :* names) (def :* defs)
156157 | Just name' == sub = case json of
157− Object m -> HM.toList m ++ rest
158+ Object m -> objectToList m ++ rest
158159 Null -> rest
159160 _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show json
160161 -- If default value: omit it.
161162 | Just x == def =
162163 rest
163164 | otherwise =
164− (T.pack name', json) : rest
165+ (stringToKey name', json) : rest
165166 where
166167 json = toJSON x
167168 name' = fieldNameModifier name
@@ -195,11 +196,11 @@ sopSwaggerGenericParseJSON = withObject "Swagger Record Object" $ \obj ->
195196 proxy = Proxy :: Proxy a
196197 opts = swaggerAesonOptions proxy
197198
198− parseAdditionalField :: Object -> (Text, Value) -> Parser ()
199+ parseAdditionalField :: Object -> Pair -> Parser ()
199200 parseAdditionalField obj (k, v) = do
200201 v' <- obj .: k
201202 unless (v == v') $ fail $
202− "Additonal field don't match for key " ++ T.unpack k
203+ "Additonal field don't match for key " ++ keyToString k
203204 ++ ": " ++ show v
204205 ++ " /= " ++ show v'
205206
@@ -230,8 +231,8 @@ sopSwaggerGenericParseJSON'' (SwaggerAesonOptions prefix _ sub) obj = go
230231 -- Note: we might strip fields of outer structure.
231232 cons <$> (withDef $ parseJSON $ Object obj) <*> rest
232233 | otherwise = case def of
233− Just def' -> cons <$> obj .:? T.pack name' .!= def' <*> rest
234− Nothing -> cons <$> obj .: T.pack name' <*> rest
234+ Just def' -> cons <$> obj .:? stringToKey name' .!= def' <*> rest
235+ Nothing -> cons <$> obj .: stringToKey name' <*> rest
235236 where
236237 cons h t = I h :* t
237238 name' = fieldNameModifier name
@@ -294,14 +295,14 @@ sopSwaggerGenericToEncoding'' (SwaggerAesonOptions prefix _ sub) = go
294295 go Nil Nil Nil = mempty
295296 go (I x :* xs) (FieldInfo name :* names) (def :* defs)
296297 | Just name' == sub = case toJSON x of
297− Object m -> pairsToSeries (HM.toList m) <> rest
298+ Object m -> pairsToSeries (objectToList m) <> rest
298299 Null -> rest
299300 _ -> error $ "sopSwaggerGenericToJSON: subjson is not an object: " ++ show (toJSON x)
300301 -- If default value: omit it.
301302 | Just x == def =
302303 rest
303304 | otherwise =
304− (T.pack name' .= x) <> rest
305+ (stringToKey name' .= x) <> rest
305306 where
306307 name' = fieldNameModifier name
307308 rest = go xs names defs
src/Data/OpenApi/Internal/Schema.hs+15−14
@@ -64,13 +64,14 @@ import GHC.Generics
6464import qualified Data.UUID.Types as UUID
6565import Type.Reflection (Typeable, typeRep)
6666
67−import Data.OpenApi.Declare
68−import Data.OpenApi.Internal
69−import Data.OpenApi.Internal.ParamSchema (ToParamSchema(..))
70−import Data.OpenApi.Lens hiding (name, schema)
71−import qualified Data.OpenApi.Lens as Swagger
72−import Data.OpenApi.SchemaOptions
73−import Data.OpenApi.Internal.TypeShape
67+import Data.OpenApi.Aeson.Compat (keyToText, objectKeys, toInsOrdHashMap)
68+import Data.OpenApi.Declare
69+import Data.OpenApi.Internal
70+import Data.OpenApi.Internal.ParamSchema (ToParamSchema (..))
71+import Data.OpenApi.Internal.TypeShape
72+import Data.OpenApi.Lens hiding (name, schema)
73+import qualified Data.OpenApi.Lens as Swagger
74+import Data.OpenApi.SchemaOptions
7475
7576import qualified Data.ByteString as BS
7677import qualified Data.ByteString.Lazy.Char8 as BSL
@@ -405,8 +406,8 @@ sketchSchema = sketch . toJSON
405406 _ -> Nothing
406407 go (Object o) = mempty
407408 & type_ ?~ OpenApiObject
408− & required .~ sort (HashMap.keys o)
409− & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
409+ & required .~ sort (objectKeys o)
410+ & properties .~ fmap (Inline . go) (toInsOrdHashMap o)
410411
411412-- | Make a restrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
412413-- Produced schema uses as much constraints as possible.
@@ -570,12 +571,12 @@ sketchStrictSchema = go . toJSON
570571 go js@(Object o) = mempty
571572 & type_ ?~ OpenApiObject
572573 & required .~ sort names
573− & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
574+ & properties .~ fmap (Inline . go) (toInsOrdHashMap o)
574575 & maxProperties ?~ fromIntegral (length names)
575576 & minProperties ?~ fromIntegral (length names)
576577 & enum_ ?~ [js]
577578 where
578− names = HashMap.keys o
579+ names = objectKeys o
579580
580581class GToSchema (f :: * -> *) where
581582 gdeclareNamedSchema :: SchemaOptions -> Proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
@@ -810,13 +811,13 @@ declareSchemaBoundedEnumKeyMapping :: forall map key value.
810811 (Bounded key, Enum key, ToJSONKey key, ToSchema key, ToSchema value)
811812 => Proxy (map key value) -> Declare (Definitions Schema) Schema
812813declareSchemaBoundedEnumKeyMapping _ = case toJSONKey :: ToJSONKeyFunction key of
813− ToJSONKeyText keyToText _ -> objectSchema keyToText
814+ ToJSONKeyText getKey _ -> objectSchema getKey
814815 ToJSONKeyValue _ _ -> declareSchema (Proxy :: Proxy [(key, value)])
815816 where
816− objectSchema keyToText = do
817+ objectSchema getKey = do
817818 valueRef <- declareSchemaRef (Proxy :: Proxy value)
818819 let allKeys = [minBound..maxBound :: key]
819− mkPair k = (keyToText k, valueRef)
820+ mkPair k = (keyToText $ getKey k, valueRef)
820821 return $ mempty
821822 & type_ ?~ OpenApiObject
822823 & properties .~ InsOrdHashMap.fromList (map mkPair allKeys)
src/Data/OpenApi/Internal/Schema/Validation.hs+23−11
@@ -1,4 +1,5 @@
11{-# OPTIONS_GHC -Wall #-}
2+{-# LANGUAGE CPP #-}
23{-# LANGUAGE DataKinds #-}
34{-# LANGUAGE DeriveFunctor #-}
45{-# LANGUAGE FlexibleInstances #-}
@@ -31,10 +32,14 @@ import Control.Lens hiding (allOf)
3132import Control.Monad (forM, forM_, when)
3233
3334import Data.Aeson hiding (Result)
35+#if MIN_VERSION_aeson(2,0,0)
36+import qualified Data.Aeson.KeyMap as KeyMap
37+#endif
3438import Data.Foldable (for_, sequenceA_,
3539 traverse_)
40+#if !MIN_VERSION_aeson(2,0,0)
3641import Data.HashMap.Strict (HashMap)
37−import qualified Data.HashMap.Strict as HashMap
42+#endif
3843import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
3944import qualified "unordered-containers" Data.HashSet as HashSet
4045import Data.Maybe (fromMaybe)
@@ -47,11 +52,12 @@ import qualified Data.Text.Lazy.Encoding as TL
4752import Data.Vector (Vector)
4853import qualified Data.Vector as Vector
4954
50−import Data.OpenApi.Declare
51−import Data.OpenApi.Internal
52−import Data.OpenApi.Internal.Utils
53−import Data.OpenApi.Internal.Schema
54−import Data.OpenApi.Lens
55+import Data.OpenApi.Aeson.Compat (hasKey, keyToText, lookupKey, objectToList)
56+import Data.OpenApi.Declare
57+import Data.OpenApi.Internal
58+import Data.OpenApi.Internal.Schema
59+import Data.OpenApi.Internal.Utils
60+import Data.OpenApi.Lens
5561
5662-- | Validate @'ToJSON'@ instance matches @'ToSchema'@ for a given value.
5763-- This can be used with QuickCheck to ensure those instances are coherent:
@@ -366,10 +372,16 @@ validateArray xs = do
366372 len = Vector.length xs
367373 allUnique = len == HashSet.size (HashSet.fromList (Vector.toList xs))
368374
369−validateObject :: HashMap Text Value -> Validation Schema ()
375+validateObject ::
376+#if MIN_VERSION_aeson(2,0,0)
377+ KeyMap.KeyMap Value
378+#else
379+ HashMap Text Value
380+#endif
381+ -> Validation Schema ()
370382validateObject o = withSchema $ \sch ->
371383 case sch ^. discriminator of
372− Just (Discriminator pname types) -> case fromJSON <$> HashMap.lookup pname o of
384+ Just (Discriminator pname types) -> case fromJSON <$> lookupKey pname o of
373385 Just (Success pvalue) ->
374386 let ref = fromMaybe pvalue $ InsOrdHashMap.lookup pvalue types
375387 -- TODO ref may be name or reference
@@ -388,15 +400,15 @@ validateObject o = withSchema $ \sch ->
388400 validateRequired
389401 validateProps
390402 where
391− size = fromIntegral (HashMap.size o)
403+ size = fromIntegral (length o)
392404
393405 validateRequired = withSchema $ \sch -> traverse_ validateReq (sch ^. required)
394406 validateReq n =
395− when (not (HashMap.member n o)) $
407+ when (not (hasKey n o)) $
396408 invalid ("property " ++ show n ++ " is required, but not found in " ++ show (encode o))
397409
398410 validateProps = withSchema $ \sch -> do
399− for_ (HashMap.toList o) $ \(k, v) ->
411+ for_ (objectToList o) $ \(keyToText -> k, v) ->
400412 case v of
401413 Null | not (k `elem` (sch ^. required)) -> valid -- null is fine for non-required property
402414 _ ->
src/Data/OpenApi/Schema/Generator.hs+3−1
@@ -25,6 +25,8 @@ import Test.QuickCheck (arbitrary)
2525import Test.QuickCheck.Gen
2626import Test.QuickCheck.Property
2727
28+import Data.OpenApi.Aeson.Compat (fromInsOrdHashMap)
29+
2830-- | Note: 'schemaGen' may 'error', if schema type is not specified,
2931-- and cannot be inferred.
3032schemaGen :: Definitions Schema -> Schema -> Gen Value
@@ -95,7 +97,7 @@ schemaGen defns schema =
9597 return . M.fromList $ zip additionalKeys (repeat . schemaGen defns $ dereference defns addlSchema)
9698 _ -> return []
9799 x <- sequence $ gens <> additionalGens
98− return . Object $ M.toHashMap x
100+ return . Object $ fromInsOrdHashMap x
99101
100102dereference :: Definitions a -> Referenced a -> a
101103dereference _ (Inline a) = a
test/Data/OpenApi/Schema/ValidationSpec.hs+17−3
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE DeriveGeneric #-}
23{-# LANGUAGE PackageImports #-}
34{-# LANGUAGE RecordWildCards #-}
@@ -8,6 +9,10 @@ module Data.OpenApi.Schema.ValidationSpec where
89import Control.Applicative
910import Control.Lens ((&), (.~), (?~))
1011import Data.Aeson
12+#if MIN_VERSION_aeson(2,0,0)
13+import qualified Data.Aeson.Key as Key
14+import qualified Data.Aeson.KeyMap as KeyMap
15+#endif
1116import Data.Aeson.Types
1217import Data.Hashable (Hashable)
1318import Data.HashMap.Strict (HashMap)
@@ -30,6 +35,7 @@ import GHC.Generics
3035
3136import Data.OpenApi
3237import Data.OpenApi.Declare
38+import Data.OpenApi.Aeson.Compat (stringToKey)
3339
3440import Test.Hspec
3541import Test.Hspec.QuickCheck
@@ -124,9 +130,9 @@ instance Arbitrary Person where
124130
125131invalidPersonToJSON :: Person -> Value
126132invalidPersonToJSON Person{..} = object
127− [ T.pack "personName" .= toJSON name
128− , T.pack "personPhone" .= toJSON phone
129− , T.pack "personEmail" .= toJSON email
133+ [ stringToKey "personName" .= toJSON name
134+ , stringToKey "personPhone" .= toJSON phone
135+ , stringToKey "personEmail" .= toJSON email
130136 ]
131137
132138-- ========================================================================
@@ -305,3 +311,11 @@ instance Arbitrary Value where
305311 , (3, Number <$> arbitrary)
306312 , (3, Bool <$> arbitrary)
307313 , (1, return Null) ]
314+
315+#if MIN_VERSION_aeson(2,0,0)
316+instance Arbitrary v => Arbitrary (KeyMap.KeyMap v) where
317+ arbitrary = KeyMap.fromList <$> arbitrary
318+
319+instance Arbitrary Key.Key where
320+ arbitrary = Key.fromText <$> arbitrary
321+#endif
test/SpecCommon.hs−8
@@ -8,14 +8,6 @@ import qualified Data.Vector as Vector
88
99import Test.Hspec
1010
11−isSubJSON :: Value -> Value -> Bool
12−isSubJSON Null _ = True
13−isSubJSON (Object x) (Object y) = HashMap.keys x == HashMap.keys i && F.and i
14− where
15− i = HashMap.intersectionWith isSubJSON x y
16−isSubJSON (Array xs) (Array ys) = Vector.length xs == Vector.length ys && F.and (Vector.zipWith isSubJSON xs ys)
17−isSubJSON x y = x == y
18−
1911(<=>) :: (Eq a, Show a, ToJSON a, FromJSON a, HasCallStack) => a -> Value -> Spec
2012x <=> js = do
2113 it "encodes correctly" $ do