Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add expected behavior for null values to decode functions #47

Merged
merged 7 commits into from Nov 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 32 additions & 13 deletions README.md
Expand Up @@ -24,28 +24,47 @@ Using [purescript-argonaut-core](https://github.com/purescript-contrib/purescrip

```purescript
someObject =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the example could be updated to use the combinators to define an instance of EncodeJson for MyType as that's a more common way to build these than this explicit jsonSingletonObject and so on.

let
objects =
let
objects =
[ jsonSingletonObject "bar" (fromString "a")
, jsonSingletonObject "bar" (fromString "b")
]
in
fromObject $ Object.fromFoldable [ Tuple "foo" (fromArray objects) ]
```

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

```purescript
main =
log $ show $ getBars someObject

getBars :: Json -> Either String (Array String)
getBars json = do
obj <- decodeJson json
foo <- obj .? "foo"
for foo \itemJson -> do
itemObj <- decodeJson itemJson
itemObj .? "bar"
newtype MyType = MyType
{ foo :: String
, bar :: Maybe Int
, baz :: Boolean
}

-- create a `DecodeJson` instance
instance decodeJsonMyType :: DecodeJson MyType where
decodeJson json = do
x <- decodeJson json
foo <- x .: "foo" -- mandatory field
bar <- x .:? "bar" -- optional field
baz <- x .:? "baz" .!= false -- optional field with default value of `false`
pure $ MyType { foo, bar, baz }

-- or pass a function
decodeMyTypes :: Json -> Either String (Array MyType)
decodeMyTypes json = do
x <- decodeJson json
arr <- x .: "myTypes"
for arr decodeJson

-- create a `EncodeJson` instance
instance encodeJsonMyType :: EncodeJson MyType where
encodeJson (MyType x) =
"foo" := x.foo ~>
"bar" :=? x.bar ~>? -- optional field
"baz" := x.baz ~>
jsonEmptyObject
```

## Contributing
Expand Down
28 changes: 21 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Data/Argonaut/Decode.purs
Expand Up @@ -4,4 +4,4 @@ module Data.Argonaut.Decode
) where

import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson)
import Data.Argonaut.Decode.Combinators (getField, (.?), getFieldOptional, (.??), defaultField, (.?=))
import Data.Argonaut.Decode.Combinators ( getField, getFieldDeprecated, getFieldOptional, getFieldOptionalDeprecated, getFieldOptional', defaultField, defaultFieldDeprecated, (.:), (.?), (.:!), (.:?), (.??), (.!=), (.?=))
3 changes: 1 addition & 2 deletions src/Data/Argonaut/Decode/Class.purs
Expand Up @@ -2,7 +2,6 @@ module Data.Argonaut.Decode.Class where

import Prelude

import Control.Alternative (class Plus)
import Data.Argonaut.Core (Json, isNull, caseJsonNull, caseJsonBoolean, caseJsonNumber, caseJsonString, toArray, toObject, toString, stringify)
import Data.Array as Arr
import Data.Bifunctor (lmap, rmap)
Expand All @@ -12,7 +11,7 @@ import Data.List (List(..), (:), fromFoldable)
import Data.List as L
import Data.Map as M
import Data.Maybe (maybe, Maybe(..))
import Data.NonEmpty (NonEmpty, singleton, (:|))
import Data.NonEmpty (NonEmpty, (:|))
import Data.String (CodePoint, codePointAt)
import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol)
import Data.Traversable (traverse)
Expand Down
96 changes: 92 additions & 4 deletions src/Data/Argonaut/Decode/Combinators.purs
@@ -1,30 +1,82 @@
module Data.Argonaut.Decode.Combinators
( getField
, getFieldDeprecated
, getFieldOptional
, getFieldOptionalDeprecated
, getFieldOptional'
, defaultField
, defaultFieldDeprecated
, (.:)
, (.?)
, (.:!)
, (.:?)
, (.??)
, (.!=)
, (.?=)
) where

import Prelude

import Data.Argonaut.Core (Json)
import Data.Argonaut.Core (Json, isNull)
import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson)
import Data.Bifunctor (lmap)
import Data.Either (Either(..))
import Data.Maybe (Maybe(..), fromMaybe, maybe)
import Foreign.Object as FO
import Prim.TypeError (class Warn, Text)

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

infix 7 getField as .?
infix 7 getField as .:

getFieldDeprecated
:: forall a. Warn ( Text "`.?` is deprecated, use `.:` instead" )
=> DecodeJson a
=> FO.Object Json
-> String
-> Either String a
getFieldDeprecated = getField

infix 7 getFieldDeprecated as .?

-- | Attempt to get the value for a given key on an `Object Json`.
-- |
-- | The result will be `Right Nothing` if the key and value are not present,
-- | or if the key is present and the value is `null`.
-- |
-- | Use this accessor if the key and value are optional in your object.
-- | If the key and value are mandatory, use `getField` (`.:`) instead.
getFieldOptional' :: forall a. DecodeJson a => FO.Object Json -> String -> Either String (Maybe a)
getFieldOptional' o s =
maybe
(pure Nothing)
decode
(FO.lookup s o)
where
decode json =
if isNull json
then pure Nothing
else Just <$> decodeJson json

infix 7 getFieldOptional' as .:?

-- | Attempt to get the value for a given key on an `Object Json`.
-- |
-- | The result will be `Right Nothing` if the key and value are not present,
-- | but will fail if the key is present but the value cannot be converted to the right type.
-- |
-- | This function will treat `null` as a value and attempt to decode it into your desired type.
-- | If you would like to treat `null` values the same as absent values, use
-- | `getFieldOptional` (`.:?`) instead.
getFieldOptional :: forall a. DecodeJson a => FO.Object Json -> String -> Either String (Maybe a)
getFieldOptional o s =
maybe
Expand All @@ -34,12 +86,48 @@ getFieldOptional o s =
where
decode json = Just <$> (elaborateFailure s <<< decodeJson) json

infix 7 getFieldOptional as .??
infix 7 getFieldOptional as .:!

getFieldOptionalDeprecated
:: forall a. Warn ( Text "`.??` is deprecated, use `.:!` or `.:?` instead" )
=> DecodeJson a
=> FO.Object Json
-> String
-> Either String (Maybe a)
getFieldOptionalDeprecated = getFieldOptional

infix 7 getFieldOptionalDeprecated as .??

-- | Helper for use in combination with `.:?` to provide default values for optional
-- | `Object Json` fields.
-- |
-- | Example usage:
-- | ```purescript
-- | newtype MyType = MyType
-- | { foo :: String
-- | , bar :: Maybe Int
-- | , baz :: Boolean
-- | }
-- |
-- | instance decodeJsonMyType :: DecodeJson MyType where
-- | decodeJson json = do
-- | x <- decodeJson json
-- | foo <- x .: "foo" -- mandatory field
-- | bar <- x .:? "bar" -- optional field
-- | baz <- x .:? "baz" .!= false -- optional field with default value of `false`
-- | pure $ MyType { foo, bar, baz }
-- | ```
defaultField :: forall a. Either String (Maybe a) -> a -> Either String a
defaultField parser default = fromMaybe default <$> parser

infix 6 defaultField as .?=
infix 6 defaultField as .!=

defaultFieldDeprecated
:: forall a. Warn ( Text "`.?=` is deprecated, use `.!=` instead" )
=> Either String (Maybe a) -> a -> Either String a
defaultFieldDeprecated = defaultField

infix 6 defaultFieldDeprecated as .?=

elaborateFailure :: ∀ a. String -> Either String a -> Either String a
elaborateFailure s e =
Expand Down