Facilities for decoding JSON records with Argonaut
bower install purescript-tolerant-argonautModule 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 _ = OnedecodeJsonWith, 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 _ _ = OneIn 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'.