Skip to content

Commit 4c16dc5

Browse files
Merge pull request #46 from elliotdavies/feature/record-codecs
Add EncodeJson/DecodeJson instances for records
2 parents f9fb69a + 87eca09 commit 4c16dc5

File tree

4 files changed

+112
-5
lines changed

4 files changed

+112
-5
lines changed

bower.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"purescript-integers": "^4.0.0",
2727
"purescript-maybe": "^4.0.0",
2828
"purescript-ordered-collections": "^1.0.0",
29-
"purescript-foreign-object": "^1.0.0"
29+
"purescript-foreign-object": "^1.0.0",
30+
"purescript-record": "^1.0.0"
3031
},
3132
"devDependencies": {
3233
"purescript-test-unit": "^14.0.0"

src/Data/Argonaut/Decode/Class.purs

+48-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
module Data.Argonaut.Decode.Class
2-
( class DecodeJson
3-
, decodeJson
4-
) where
1+
module Data.Argonaut.Decode.Class where
52

63
import Prelude
74

@@ -13,10 +10,15 @@ import Data.List (List(..), (:), fromFoldable)
1310
import Data.Map as M
1411
import Data.Maybe (maybe, Maybe(..))
1512
import Data.String (CodePoint, codePointAt)
13+
import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol)
1614
import Data.Traversable (traverse)
1715
import Data.TraversableWithIndex (traverseWithIndex)
1816
import Data.Tuple (Tuple(..))
1917
import Foreign.Object as FO
18+
import Prim.Row as Row
19+
import Prim.RowList as RL
20+
import Record as Record
21+
import Type.Data.RowList (RLProxy(..))
2022

2123
class DecodeJson a where
2224
decodeJson :: Json -> Either String a
@@ -98,3 +100,45 @@ decodeJArray = maybe (Left "Value is not an Array") Right <<< toArray
98100

99101
decodeJObject :: Json -> Either String (FO.Object Json)
100102
decodeJObject = maybe (Left "Value is not an Object") Right <<< toObject
103+
104+
instance decodeRecord
105+
:: ( GDecodeJson row list
106+
, RL.RowToList row list
107+
)
108+
=> DecodeJson (Record row) where
109+
110+
decodeJson json =
111+
case toObject json of
112+
Just object -> gDecodeJson object (RLProxy :: RLProxy list)
113+
Nothing -> Left "Could not convert JSON to object"
114+
115+
class GDecodeJson (row :: # Type) (list :: RL.RowList) | list -> row where
116+
gDecodeJson :: FO.Object Json -> RLProxy list -> Either String (Record row)
117+
118+
instance gDecodeJsonNil :: GDecodeJson () RL.Nil where
119+
gDecodeJson _ _ = Right {}
120+
121+
instance gDecodeJsonCons
122+
:: ( DecodeJson value
123+
, GDecodeJson rowTail tail
124+
, IsSymbol field
125+
, Row.Cons field value rowTail row
126+
, Row.Lacks field rowTail
127+
)
128+
=> GDecodeJson row (RL.Cons field value tail) where
129+
130+
gDecodeJson object _ = do
131+
let sProxy :: SProxy field
132+
sProxy = SProxy
133+
134+
fieldName = reflectSymbol sProxy
135+
136+
rest <- gDecodeJson object (RLProxy :: RLProxy tail)
137+
138+
case FO.lookup fieldName object of
139+
Just jsonVal -> do
140+
val <- decodeJson jsonVal
141+
Right $ Record.insert sProxy val rest
142+
143+
Nothing ->
144+
Left $ "JSON was missing expected field: " <> fieldName

src/Data/Argonaut/Encode/Class.purs

+37
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ import Data.Maybe (Maybe(..))
1111
import Data.String (CodePoint)
1212
import Data.String.CodePoints as CP
1313
import Data.String.CodeUnits as CU
14+
import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol)
1415
import Data.Tuple (Tuple(..))
1516
import Foreign.Object as FO
17+
import Prim.Row as Row
18+
import Prim.RowList as RL
19+
import Record as Record
20+
import Type.Data.RowList (RLProxy(..))
1621

1722
class EncodeJson a where
1823
encodeJson :: a -> Json
@@ -70,3 +75,35 @@ instance encodeMap :: (Ord a, EncodeJson a, EncodeJson b) => EncodeJson (M.Map a
7075

7176
instance encodeVoid :: EncodeJson Void where
7277
encodeJson = absurd
78+
79+
instance encodeRecord
80+
:: ( GEncodeJson row list
81+
, RL.RowToList row list
82+
)
83+
=> EncodeJson (Record row) where
84+
85+
encodeJson rec = fromObject $ gEncodeJson rec (RLProxy :: RLProxy list)
86+
87+
class GEncodeJson (row :: # Type) (list :: RL.RowList) where
88+
gEncodeJson :: Record row -> RLProxy list -> FO.Object Json
89+
90+
instance gEncodeJsonNil :: GEncodeJson row RL.Nil where
91+
gEncodeJson _ _ = FO.empty
92+
93+
instance gEncodeJsonCons
94+
:: ( EncodeJson value
95+
, GEncodeJson row tail
96+
, IsSymbol field
97+
, Row.Cons field value tail' row
98+
)
99+
=> GEncodeJson row (RL.Cons field value tail) where
100+
101+
gEncodeJson row _ =
102+
let
103+
sProxy :: SProxy field
104+
sProxy = SProxy
105+
in
106+
FO.insert
107+
(reflectSymbol sProxy)
108+
(encodeJson $ Record.get sProxy row)
109+
(gEncodeJson row $ RLProxy :: RLProxy tail)

test/Test/Main.purs

+25
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,44 @@ import Data.Tuple (Tuple(..))
1717
import Effect (Effect)
1818
import Foreign.Object as FO
1919
import Test.QuickCheck (Result(..), (<?>), (===))
20+
import Test.QuickCheck.Arbitrary (arbitrary)
2021
import Test.QuickCheck.Gen (Gen, resize, suchThat)
2122
import Test.Unit (TestSuite, test, suite, failure)
2223
import Test.Unit.Assert as Assert
2324
import Test.Unit.Main (runTest)
2425
import Test.Unit.QuickCheck (quickCheck)
2526

27+
2628
main :: Effect Unit
2729
main = runTest do
2830
suite "Either Check" eitherCheck
2931
suite "Encode/Decode Checks" encodeDecodeCheck
32+
suite "Encode/Decode Record Checks" encodeDecodeRecordCheck
3033
suite "Combinators Checks" combinatorsCheck
3134
suite "Error Message Checks" errorMsgCheck
3235

36+
37+
genTestRecord
38+
:: Gen (Record
39+
( i :: Int
40+
, n :: Number
41+
, s :: String
42+
))
43+
genTestRecord = arbitrary
44+
45+
encodeDecodeRecordCheck :: TestSuite
46+
encodeDecodeRecordCheck = do
47+
test "Testing that any record can be encoded and then decoded" do
48+
quickCheck rec_encode_then_decode
49+
50+
where
51+
rec_encode_then_decode :: Gen Result
52+
rec_encode_then_decode = do
53+
rec <- genTestRecord
54+
let redecoded = decodeJson (encodeJson rec)
55+
pure $ Right rec == redecoded <?> (show redecoded <> " /= Right " <> show rec)
56+
57+
3358
genTestJson :: Gen Json
3459
genTestJson = resize 5 genJson
3560

0 commit comments

Comments
 (0)