Facilities for decoding JSON records with Argonaut
bower install purescript-tolerant-argonaut
Module documentation is published on Pursuit.
This package provides Data.Argonaut.Decode-compatible decoding utilities.
It also provides a decodeJson
function with behavior modeled after the standard Argonaut decodeJson
, the salient difference being use of Builder
insertions instead of Record
insertions.
Whereas decodeJson
from Data.Argonaut.Decode fails to decode a JSON object by design if the object lacks a field, decodeJson
from Data.Argonaut.Decode.Struct.Tolerant interprets a field's absence, should the field have a value of a Plus-instance type, as the instance's empty value.
import Data.Argonaut.Decode (decodeJson) as D
import Data.Argonaut.Decode.Struct.Tolerant (decodeJson) as T
import Data.Argonaut.Encode (encodeJson)
emptyJson = encodeJson {}
value1 = D.decodeJson emptyJson :: Either String { a :: Maybe Int }
-- value1 == Left "JSON was missing expected field: a"
value2 = T.decodeJson emptyJson :: Either String { a :: Maybe Int }
-- value2 == Right { a: Nothing }
JSON representations of data do not always match data structures in code. decodeJsonPer, by default, delegates to standard purescript-argonaut-codecs JSON decoding. However, it also permits customized decoding of specific fields when their representation in JSON does not accord with their target representation.
import Data.Argonaut.Decode (decodeJson) as D
import Data.Argonaut.Decode.Struct (decodeJsonPer) as T
import Data.Argonaut.Encode (encodeJson)
data Scoops = One | Two
type Flavor = String
type IceCream = { flavor :: Flavor, scoops :: Scoops }
jsonIceCream = encodeJson { flavor: "vanilla", scoops: 2 }
iceCream :: Either String IceCream
iceCream =
T.decodeJsonPer
{ scoops: \scoopsJson -> convert <$> D.decodeJson scoopsJson }
jsonIceCream
where
convert :: Int -> Scoops
convert 2 = Two
convert _ = One
decodeJsonWith, like decodeJsonPer
, overrides standard JSON decoding for specified fields of a JSON object. However, unlike its counterpart, decodeJsonWith
also enables a simple kind of structure folding. Data derivable via standard decoding is accumulated and made available to the decoding customizations of the remaining fields.
An example should make this clearer:
import Data.Argonaut.Decode (decodeJson) as D
import Data.Argonaut.Decode.Struct (decodeJsonWith) as T
import Data.Argonaut.Encode (encodeJson)
data Scoops = One | Two
type Flavor = String
type Promotion = Boolean
type IceCream = { flavor :: Flavor, promotion :: Promotion, scoops :: Scoops }
jsonIceCream = encodeJson { flavor: "vanilla", promotion: true, scoops: 2 }
iceCream :: Either String IceCream
iceCream =
T.decodeJsonWith
{ scoops: \scoopsJson { promotion } -> convert promotion <$> D.decodeJson scoopsJson }
jsonIceCream
where
convert :: Promotion -> Int -> Scoops
convert true _ = Two
convert _ 2 = Two
convert _ _ = One
In the above example, all fields of a JSON object are decoded in standard fashion, except the 'scoops' field, since its decoding depends on another field of the JSON object, the 'promotion' field. As decoding for the 'promotion' field is not overridden, decodeJsonWith can make the derivation of promotion data available to the customized decoder for 'scoops'.