Skip to content

Commit b85cca8

Browse files
authored
Merge pull request #11 from garyb/nest-for-tagged
Nest for tagged
2 parents 6d10c99 + 8d37002 commit b85cca8

File tree

2 files changed

+75
-3
lines changed

2 files changed

+75
-3
lines changed

src/Data/Codec/Argonaut/Migration.purs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module Data.Codec.Argonaut.Migration
33
, updateField
44
, addDefaultOrUpdateField
55
, renameField
6+
, nestForTagged
67
) where
78

89
import Prelude
@@ -11,8 +12,9 @@ import Data.Argonaut.Core as J
1112
import Data.Codec (basicCodec)
1213
import Data.Codec.Argonaut (JsonCodec)
1314
import Data.Maybe (Maybe(..), maybe, fromMaybe)
14-
import Data.Tuple (uncurry)
1515
import Data.StrMap as SM
16+
import Data.StrMap.ST as SMST
17+
import Data.Tuple (Tuple(..), uncurry)
1618

1719
-- | When dealing with a JSON object that may be missing a field, this codec
1820
-- | can be used to alter the JSON before parsing to ensure a default value is
@@ -42,6 +44,46 @@ renameField oldName newName = basicCodec (pure <<< dec) id
4244
rename J.JObject J.JObject
4345
rename obj = maybe obj (uncurry (SM.insert newName)) (SM.pop oldName obj)
4446

47+
-- | Prepares an object from a legacy codec for use in a `Variant` or
48+
-- | `taggedSum` codec.
49+
-- |
50+
-- | For an input like:
51+
-- | ```{ "tag": "tag", "x": 1, "y": 2, "z": 3 }```
52+
-- | the result will be:
53+
-- | ```{ "tag": "tag", "value": { "x": 1, "y": 2, "z": 3 } }```
54+
-- |
55+
-- | For an input like:
56+
-- | ```{ "tag": "tag", "value": 1, "foo": 2 }```
57+
-- | the result will be:
58+
-- | ```{ "tag": "tag", "value": { "value": 1, "foo": 2 }```
59+
-- |
60+
-- | If the value is already in the expected form, where there is only `value`
61+
-- | and no other keys (aside from `tag`):
62+
-- | ```{ "tag": "tag", "value": true }```
63+
-- | the result will be the same as the input.
64+
-- |
65+
-- | If the tag field is missing from the input, it will also be missing in the
66+
-- | output.
67+
nestForTagged JsonCodec J.Json
68+
nestForTagged = basicCodec (pure <<< dec) id
69+
where
70+
dec J.Json J.Json
71+
dec j = J.foldJsonObject j (J.fromObject <<< rewrite) j
72+
rewrite J.JObject J.JObject
73+
rewrite obj =
74+
case SM.pop "tag" obj of
75+
NothingSM.pureST do
76+
result ← SMST.new
77+
SMST.poke result "value" (mkValue obj)
78+
Just (Tuple tagValue obj') → SM.pureST do
79+
result ← SMST.new
80+
_ ← SMST.poke result "tag" tagValue
81+
SMST.poke result "value" (mkValue obj')
82+
mkValue J.JObject J.Json
83+
mkValue obj = case SM.pop "value" obj of
84+
Just (Tuple valueValue obj') | SM.isEmpty obj' → valueValue
85+
_ → J.fromObject obj
86+
4587
alterField String (Maybe J.Json Maybe J.Json) JsonCodec J.Json
4688
alterField field f = basicCodec (pure <<< dec) id
4789
where

test/Test/Migration.purs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ import Prelude
44

55
import Control.Monad.Eff.Console (log)
66
import Data.Argonaut.Core as J
7+
import Data.Argonaut.Gen as GenJ
78
import Data.Codec ((<~<))
89
import Data.Codec.Argonaut.Common as JA
910
import Data.Codec.Argonaut.Migration as JAM
1011
import Data.Either (Either(..))
1112
import Data.Maybe (Maybe(..), fromMaybe, maybe)
12-
import Data.String.Gen (genAsciiString)
1313
import Data.StrMap as SM
14+
import Data.StrMap.Gen as GenSM
15+
import Data.String.Gen (genAsciiString)
16+
import Data.Tuple (Tuple(..))
1417
import Test.QuickCheck (Result(..), QC, quickCheck, (===))
1518
import Test.QuickCheck.Gen (Gen)
16-
import Test.Util (genJObject)
19+
import Test.Util (genJObject, propCodec)
1720

1821
main :: QC () Unit
1922
main = do
@@ -35,6 +38,12 @@ main = do
3538
log "Checking renameField renames a field"
3639
quickCheck propDefaultFieldPreservesOriginal
3740

41+
log "Checking nestForTagged moves all expected fields under `value`"
42+
quickCheck propNestForTaggedMovesUnderValue
43+
44+
log "Checking nestForTagged is idempotent"
45+
quickCheck propNestForTaggedIdempotent
46+
3847
propDefaultFieldAdded Gen Result
3948
propDefaultFieldAdded = do
4049
let expectedValue = J.fromString "it's here"
@@ -87,6 +96,27 @@ propRenameField = do
8796
pure $ testMigrationCodec { key: newKey, expectedValue, input }
8897
$ JA.jobject <~< JAM.renameField oldKey newKey
8998

99+
propNestForTaggedMovesUnderValue Gen Result
100+
propNestForTaggedMovesUnderValue = do
101+
values ← GenSM.genStrMap genAsciiString GenJ.genJson
102+
-- TODO: only-value
103+
let expectedValue = J.fromObject (SM.delete "tag" values)
104+
pure $ testMigrationCodec { key: "value", expectedValue, input: values }
105+
$ JA.jobject <~< JAM.nestForTagged
106+
107+
propNestForTaggedIdempotent Gen Result
108+
propNestForTaggedIdempotent = do
109+
propCodec genTagged JAM.nestForTagged
110+
where
111+
genTagged = do
112+
tag ← genAsciiString
113+
expectedValue ← GenJ.genJson
114+
pure $ J.fromObject $
115+
SM.fromFoldable
116+
[ Tuple "tag" (J.fromString tag)
117+
, Tuple "value" expectedValue
118+
]
119+
90120
testMigrationCodec
91121
{ key String
92122
, expectedValue J.Json

0 commit comments

Comments
 (0)