Skip to content

Commit 36793c8

Browse files
Add support for JSON serialisation. (#236)
2 parents 67cc826 + bb58cf6 commit 36793c8

File tree

61 files changed

+34259
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+34259
-2
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{-# OPTIONS_GHC -Wno-orphans #-}
2+
3+
-- |
4+
-- Copyright: © 2022–2025 Jonathan Knowles
5+
-- License: Apache-2.0
6+
--
7+
module Data.MonoidMap.JSON where
8+
9+
import Prelude
10+
11+
import Data.Aeson
12+
( FromJSON (parseJSON)
13+
, FromJSONKey
14+
, ToJSON (toEncoding, toJSON)
15+
, ToJSONKey
16+
)
17+
import Data.Monoid.Null
18+
( MonoidNull
19+
)
20+
import Data.MonoidMap
21+
( MonoidMap
22+
)
23+
24+
import qualified Data.MonoidMap as MonoidMap
25+
26+
instance
27+
( ToJSONKey k
28+
, ToJSON v
29+
)
30+
=> ToJSON (MonoidMap k v)
31+
where
32+
toEncoding = toEncoding . MonoidMap.toMap
33+
toJSON = toJSON . MonoidMap.toMap
34+
35+
instance
36+
( FromJSONKey k
37+
, Ord k
38+
, FromJSON v
39+
, MonoidNull v
40+
)
41+
=> FromJSON (MonoidMap k v)
42+
where
43+
parseJSON = fmap (fmap MonoidMap.fromMap) parseJSON
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
{-# LANGUAGE ExistentialQuantification #-}
2+
{-# OPTIONS_GHC -fno-warn-orphans #-}
3+
4+
-- |
5+
-- Copyright: © 2022–2025 Jonathan Knowles
6+
-- License: Apache-2.0
7+
--
8+
module Data.MonoidMap.JSONSpec
9+
( spec
10+
)
11+
where
12+
13+
import Prelude
14+
15+
import Control.Monad
16+
( forM_
17+
)
18+
import Data.MonoidMap
19+
( MonoidMap
20+
)
21+
import Data.MonoidMap.JSON
22+
(
23+
)
24+
import Data.Proxy
25+
( Proxy (Proxy)
26+
)
27+
import Data.Text
28+
( Text
29+
)
30+
import Test.Aeson.Internal.GoldenSpecs
31+
( goldenSpecs
32+
)
33+
import Test.Common
34+
( Test
35+
, TestKey
36+
, TestValueType (TestValueType)
37+
, makeSpec
38+
, testValueTypesAll
39+
)
40+
import Test.Hspec
41+
( Spec
42+
, describe
43+
)
44+
import Test.QuickCheck.Classes
45+
( jsonLaws
46+
)
47+
import Test.QuickCheck.Classes.Hspec
48+
( testLaws
49+
)
50+
51+
import qualified Test.Aeson.Internal.Utils as Golden
52+
53+
spec :: Spec
54+
spec = do
55+
describe "JSON"
56+
$ forM_ testKeyValueTypes
57+
$ \(TestKeyType k, TestValueType v) -> specForTypes k v
58+
where
59+
testKeyValueTypes =
60+
[(kt, vt) | kt <- testKeyTypes, vt <- testValueTypesAll]
61+
62+
specForTypes :: forall k v. (Test k v) => Proxy k -> Proxy v -> Spec
63+
specForTypes = makeSpec $ do
64+
testLaws @(MonoidMap k v) jsonLaws
65+
goldenSpecs goldenSettings (Proxy @(MonoidMap k v))
66+
67+
goldenSettings :: Golden.Settings
68+
goldenSettings =
69+
Golden.defaultSettings
70+
{ Golden.goldenDirectoryOption =
71+
Golden.CustomDirectoryName "golden"
72+
, Golden.comparisonFile =
73+
Golden.OverwriteGoldenFile
74+
, Golden.randomMismatchOption =
75+
Golden.RandomMismatchError
76+
, Golden.useModuleNameAsSubDirectory =
77+
False
78+
, Golden.sampleSize =
79+
10
80+
}
81+
82+
data TestKeyType = forall k. (TestKey k) => TestKeyType (Proxy k)
83+
84+
testKeyTypes :: [TestKeyType]
85+
testKeyTypes =
86+
mconcat [testKeyTypes_textual, testKeyTypes_nonTextual]
87+
where
88+
-- A selection of key types for which keys are encoded as JSON strings.
89+
-- For these types, 'MonoidMap' objects are encoded as JSON objects.
90+
testKeyTypes_textual =
91+
[ TestKeyType (Proxy @Int)
92+
, TestKeyType (Proxy @Text)
93+
]
94+
-- A selection of key types for which keys are NOT encoded as JSON strings.
95+
-- For these types, 'MonoidMap' objects are encoded as JSON arrays.
96+
testKeyTypes_nonTextual =
97+
[ TestKeyType (Proxy @[Int])
98+
, TestKeyType (Proxy @(Int, Int))
99+
]

components/monoidmap-test/Test/Common.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
module Test.Common
1212
( Key
1313
, Test
14+
, TestKey
1415
, TestValueType (..)
1516
, testValueTypesAll
1617
, testValueTypesGroup
@@ -30,6 +31,12 @@ module Test.Common
3031

3132
import Prelude
3233

34+
import Data.Aeson
35+
( FromJSON
36+
, FromJSONKey
37+
, ToJSON
38+
, ToJSONKey
39+
)
3340
import Data.Group
3441
( Group )
3542
import Data.Kind
@@ -46,6 +53,8 @@ import Data.Monoid.Null
4653
( MonoidNull )
4754
import Data.MonoidMap
4855
( MonoidMap )
56+
import Data.MonoidMap.JSON
57+
()
4958
import Data.Proxy
5059
( Proxy (Proxy) )
5160
import Data.Semigroup.Cancellative
@@ -156,6 +165,8 @@ type TestKey k =
156165
, Ord k
157166
, Show k
158167
, Typeable k
168+
, ToJSONKey k
169+
, FromJSONKey k
159170
)
160171

161172
type TestValue v =
@@ -166,6 +177,8 @@ type TestValue v =
166177
, MonoidNull v
167178
, Show v
168179
, Typeable v
180+
, ToJSON v
181+
, FromJSON v
169182
)
170183

171184
--------------------------------------------------------------------------------

components/monoidmap-test/Test/Key.hs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ where
1818

1919
import Prelude
2020

21+
import Data.Aeson.Types
22+
( FromJSON (parseJSON)
23+
, FromJSONKey (fromJSONKey)
24+
, FromJSONKeyFunction (FromJSONKeyTextParser)
25+
, Parser
26+
, ToJSON (toEncoding, toJSON)
27+
, ToJSONKey (toJSONKey)
28+
, toJSONKeyText
29+
, withText
30+
)
31+
import Data.Text
32+
( Text
33+
)
2134
import GHC.Generics
2235
( Generic
2336
)
@@ -34,10 +47,15 @@ import Test.QuickCheck.Quid
3447
, Quid
3548
, Size (Size)
3649
)
50+
import Text.Read
51+
( readMaybe
52+
)
3753

38-
newtype Key (size :: Nat) = Key (Latin Quid)
54+
import qualified Data.Text as Text
55+
56+
newtype Key (size :: Nat) = Key Quid
3957
deriving stock (Eq, Generic, Ord)
40-
deriving newtype (Read, Show)
58+
deriving (Read, Show) via Latin Quid
4159
deriving (Arbitrary) via Size size Quid
4260
deriving (CoArbitrary) via Quid
4361
deriving anyclass (Function)
@@ -46,3 +64,28 @@ type Key1 = Key 1
4664
type Key2 = Key 2
4765
type Key4 = Key 4
4866
type Key8 = Key 8
67+
68+
instance ToJSON (Key size) where
69+
toEncoding = toEncoding . toText
70+
toJSON = toJSON . toText
71+
72+
instance ToJSONKey (Key size) where
73+
toJSONKey = toJSONKeyText toText
74+
75+
instance FromJSON (Key size) where
76+
parseJSON = withText "Key" parseFromText
77+
78+
instance FromJSONKey (Key size) where
79+
fromJSONKey = FromJSONKeyTextParser parseFromText
80+
81+
toText :: Key size -> Text
82+
toText = Text.dropAround (== '\"') . Text.pack . show
83+
84+
maybeFromText :: Text -> Maybe (Key size)
85+
maybeFromText = readMaybe . show . Text.unpack
86+
87+
parseFromText :: Text -> Parser (Key size)
88+
parseFromText =
89+
maybe (fail failureMessage) pure . maybeFromText
90+
where
91+
failureMessage = "Failed to parse key from JSON"

0 commit comments

Comments
 (0)