Skip to content

Commit eabe759

Browse files
Merge pull request #47 from davezuch/master
Add expected behavior for null values to decode functions
2 parents f8334e9 + 8b169ed commit eabe759

File tree

6 files changed

+294
-46
lines changed

6 files changed

+294
-46
lines changed

README.md

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,47 @@ Using [purescript-argonaut-core](https://github.com/purescript-contrib/purescrip
2424

2525
```purescript
2626
someObject =
27-
let
28-
objects =
27+
let
28+
objects =
2929
[ jsonSingletonObject "bar" (fromString "a")
3030
, jsonSingletonObject "bar" (fromString "b")
3131
]
3232
in
3333
fromObject $ Object.fromFoldable [ Tuple "foo" (fromArray objects) ]
3434
```
3535

36-
The `decodeJson` and `.?` functions provided in this module make it straightforward to interrogate the `Json` object:
36+
The `decodeJson`, `.:`, `.:?`, and `.!=` functions provided in this module make it straightforward to interrogate the `Json` object:
3737

3838
```purescript
39-
main =
40-
log $ show $ getBars someObject
41-
42-
getBars :: Json -> Either String (Array String)
43-
getBars json = do
44-
obj <- decodeJson json
45-
foo <- obj .? "foo"
46-
for foo \itemJson -> do
47-
itemObj <- decodeJson itemJson
48-
itemObj .? "bar"
39+
newtype MyType = MyType
40+
{ foo :: String
41+
, bar :: Maybe Int
42+
, baz :: Boolean
43+
}
44+
45+
-- create a `DecodeJson` instance
46+
instance decodeJsonMyType :: DecodeJson MyType where
47+
decodeJson json = do
48+
x <- decodeJson json
49+
foo <- x .: "foo" -- mandatory field
50+
bar <- x .:? "bar" -- optional field
51+
baz <- x .:? "baz" .!= false -- optional field with default value of `false`
52+
pure $ MyType { foo, bar, baz }
53+
54+
-- or pass a function
55+
decodeMyTypes :: Json -> Either String (Array MyType)
56+
decodeMyTypes json = do
57+
x <- decodeJson json
58+
arr <- x .: "myTypes"
59+
for arr decodeJson
60+
61+
-- create a `EncodeJson` instance
62+
instance encodeJsonMyType :: EncodeJson MyType where
63+
encodeJson (MyType x) =
64+
"foo" := x.foo ~>
65+
"bar" :=? x.bar ~>? -- optional field
66+
"baz" := x.baz ~>
67+
jsonEmptyObject
4968
```
5069

5170
## Contributing

package-lock.json

Lines changed: 21 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Data/Argonaut/Decode.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ module Data.Argonaut.Decode
44
) where
55

66
import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson)
7-
import Data.Argonaut.Decode.Combinators (getField, (.?), getFieldOptional, (.??), defaultField, (.?=))
7+
import Data.Argonaut.Decode.Combinators ( getField, getFieldDeprecated, getFieldOptional, getFieldOptionalDeprecated, getFieldOptional', defaultField, defaultFieldDeprecated, (.:), (.?), (.:!), (.:?), (.??), (.!=), (.?=))

src/Data/Argonaut/Decode/Class.purs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ module Data.Argonaut.Decode.Class where
22

33
import Prelude
44

5-
import Control.Alternative (class Plus)
65
import Data.Argonaut.Core (Json, isNull, caseJsonNull, caseJsonBoolean, caseJsonNumber, caseJsonString, toArray, toObject, toString, stringify)
76
import Data.Array as Arr
87
import Data.Bifunctor (lmap, rmap)
@@ -12,7 +11,7 @@ import Data.List (List(..), (:), fromFoldable)
1211
import Data.List as L
1312
import Data.Map as M
1413
import Data.Maybe (maybe, Maybe(..))
15-
import Data.NonEmpty (NonEmpty, singleton, (:|))
14+
import Data.NonEmpty (NonEmpty, (:|))
1615
import Data.String (CodePoint, codePointAt)
1716
import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol)
1817
import Data.Traversable (traverse)

src/Data/Argonaut/Decode/Combinators.purs

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,82 @@
11
module Data.Argonaut.Decode.Combinators
22
( getField
3+
, getFieldDeprecated
34
, getFieldOptional
5+
, getFieldOptionalDeprecated
6+
, getFieldOptional'
47
, defaultField
8+
, defaultFieldDeprecated
9+
, (.:)
510
, (.?)
11+
, (.:!)
12+
, (.:?)
613
, (.??)
14+
, (.!=)
715
, (.?=)
816
) where
917

1018
import Prelude
1119

12-
import Data.Argonaut.Core (Json)
20+
import Data.Argonaut.Core (Json, isNull)
1321
import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson)
1422
import Data.Bifunctor (lmap)
1523
import Data.Either (Either(..))
1624
import Data.Maybe (Maybe(..), fromMaybe, maybe)
1725
import Foreign.Object as FO
26+
import Prim.TypeError (class Warn, Text)
1827

28+
-- | Attempt to get the value for a given key on an `Object Json`.
29+
-- |
30+
-- | Use this accessor if the key and value *must* be present in your object.
31+
-- | If the key and value are optional, use `getFieldOptional'` (`.:?`) instead.
1932
getField :: forall a. DecodeJson a => FO.Object Json -> String -> Either String a
2033
getField o s =
2134
maybe
2235
(Left $ "Expected field " <> show s)
2336
(elaborateFailure s <<< decodeJson)
2437
(FO.lookup s o)
2538

26-
infix 7 getField as .?
39+
infix 7 getField as .:
2740

41+
getFieldDeprecated
42+
:: forall a. Warn ( Text "`.?` is deprecated, use `.:` instead" )
43+
=> DecodeJson a
44+
=> FO.Object Json
45+
-> String
46+
-> Either String a
47+
getFieldDeprecated = getField
48+
49+
infix 7 getFieldDeprecated as .?
50+
51+
-- | Attempt to get the value for a given key on an `Object Json`.
52+
-- |
53+
-- | The result will be `Right Nothing` if the key and value are not present,
54+
-- | or if the key is present and the value is `null`.
55+
-- |
56+
-- | Use this accessor if the key and value are optional in your object.
57+
-- | If the key and value are mandatory, use `getField` (`.:`) instead.
58+
getFieldOptional' :: forall a. DecodeJson a => FO.Object Json -> String -> Either String (Maybe a)
59+
getFieldOptional' o s =
60+
maybe
61+
(pure Nothing)
62+
decode
63+
(FO.lookup s o)
64+
where
65+
decode json =
66+
if isNull json
67+
then pure Nothing
68+
else Just <$> decodeJson json
69+
70+
infix 7 getFieldOptional' as .:?
71+
72+
-- | Attempt to get the value for a given key on an `Object Json`.
73+
-- |
74+
-- | The result will be `Right Nothing` if the key and value are not present,
75+
-- | but will fail if the key is present but the value cannot be converted to the right type.
76+
-- |
77+
-- | This function will treat `null` as a value and attempt to decode it into your desired type.
78+
-- | If you would like to treat `null` values the same as absent values, use
79+
-- | `getFieldOptional` (`.:?`) instead.
2880
getFieldOptional :: forall a. DecodeJson a => FO.Object Json -> String -> Either String (Maybe a)
2981
getFieldOptional o s =
3082
maybe
@@ -34,12 +86,48 @@ getFieldOptional o s =
3486
where
3587
decode json = Just <$> (elaborateFailure s <<< decodeJson) json
3688

37-
infix 7 getFieldOptional as .??
89+
infix 7 getFieldOptional as .:!
90+
91+
getFieldOptionalDeprecated
92+
:: forall a. Warn ( Text "`.??` is deprecated, use `.:!` or `.:?` instead" )
93+
=> DecodeJson a
94+
=> FO.Object Json
95+
-> String
96+
-> Either String (Maybe a)
97+
getFieldOptionalDeprecated = getFieldOptional
3898

99+
infix 7 getFieldOptionalDeprecated as .??
100+
101+
-- | Helper for use in combination with `.:?` to provide default values for optional
102+
-- | `Object Json` fields.
103+
-- |
104+
-- | Example usage:
105+
-- | ```purescript
106+
-- | newtype MyType = MyType
107+
-- | { foo :: String
108+
-- | , bar :: Maybe Int
109+
-- | , baz :: Boolean
110+
-- | }
111+
-- |
112+
-- | instance decodeJsonMyType :: DecodeJson MyType where
113+
-- | decodeJson json = do
114+
-- | x <- decodeJson json
115+
-- | foo <- x .: "foo" -- mandatory field
116+
-- | bar <- x .:? "bar" -- optional field
117+
-- | baz <- x .:? "baz" .!= false -- optional field with default value of `false`
118+
-- | pure $ MyType { foo, bar, baz }
119+
-- | ```
39120
defaultField :: forall a. Either String (Maybe a) -> a -> Either String a
40121
defaultField parser default = fromMaybe default <$> parser
41122

42-
infix 6 defaultField as .?=
123+
infix 6 defaultField as .!=
124+
125+
defaultFieldDeprecated
126+
:: forall a. Warn ( Text "`.?=` is deprecated, use `.!=` instead" )
127+
=> Either String (Maybe a) -> a -> Either String a
128+
defaultFieldDeprecated = defaultField
129+
130+
infix 6 defaultFieldDeprecated as .?=
43131

44132
elaborateFailure :: a. String -> Either String a -> Either String a
45133
elaborateFailure s e =

0 commit comments

Comments
 (0)