Optional Values and Recovery
Sometimes, you may need to decode nullable JSON values, or JSON values that can be one of several different types. bs-decode
provides several functions to help you in these cases.
Optional Values
Simple decoders can be wrapped in D.optional
to allow them to tolerate null
values and decode into option('a)
rather than 'a
.
let jsonNull = Js.Json.null;
let jsonStr = Js.Json.string("foo");
Decode.(optional(string, jsonNull)); // Ok(None)
Decode.(optional(string, jsonStr)); // Ok(Some("foo"))
Decode.(optional(boolean, jsonStr)); // Error(Val(`ExpectedBoolean, jsonStr))
Note that unlike Elm's Json.Decode
, optional values aren't atuomatically recovered as None
. An optional string
decoder will fail when given a JSON value that isn't a string.
Optional Fields
This gets into decoding objects, which is covered elsewhere. But it's important to be aware of the specialized optionalField
function, in addition to the normal optional
function. optionalField
will tolerate both missing fields in a JSON object as well as present fields with null
values, however like the normal optional
function, this won't automatically recover from unexpected JSON.
let json: Js.Json.t = [%bs.raw {|
{
"name": "Michael",
"age": null
}
|}];
Decode.(optionalField("name", string, json)); // Ok(Some("Michael"))
Decode.(optionalField("age", intFromNumber, json)); // Ok(None)
Decode.(optionalField("isAdmin", boolean, json)); // Ok(None)
Decode.(optionalField("name", boolean, json)); // Error(...)
// compare with `optional` which is probably not what you want:
// Error(Val(`ExpectedInt, ...))
Decode.(optional(field("age", intFromNumber), json));
// Error(Obj(NonEmptyList.pure(("isAdmin", MissingField))))
Decode.(field("isAdmin", optional(boolean), json));
Try Multiple Decoders
If a JSON value could be one of several types, you can try multiple decoders in order using alt
or oneOf
. Decoding will end successfully on the first success, or with a TriedMultiple
error once all provided decoders have been attempted.
Note that each attempt is evaluated lazily, so subsequent decoders will only be run if no success has been found yet.
// each decoder in `oneOf` has to return the same type
type t =
| B(bool)
| S(string)
| I(int)
| F(float);
let json = Js.Json.string("foo");
// functions from `json => Result.t(t, ...)`
let decodeB = Decode.(boolean |> map(v => B(v)));
let decodeS = Decode.(string |> p(v => S(v)));
let decodeI = Decode.(intFromNumber |> map(v => I(v)));
let decodeF = Decode.(floatFromNumber |> map(v => F(v)));
// here comes the part you actually care about
Decode.oneOf(decodeB, [decodeS, decodeI], json); // Ok(S("foo"))
Decode.oneOf(decodeB, [decodeI], json); // Error(Val(`ExpectedInt, ...))