Compositional JSON encode/decode library for BuckleScript.
The Decode module in particular provides a basic set of decoder functions to be composed into more complex decoders. A
decoder is a function that takes a Js.Json.t
and either returns a value of the desired type if successful or raises a
DecodeError
exception if not. Other functions accept a decoder and produce another decoder. Like array
, which when
given a decoder for type t
will return a decoder that tries to produce a value of type t array
. So to decode an
int array
you combine Json.Decode.int
with Json.Decode.array
into Json.Decode.(array int)
. An array of arrays of
ints? Json.Decode.(array (array int))
. Dict containing arrays of ints? Json.Decode.(dict (array int))
.
type line = {
start: point,
end_: point,
thickness: option(int)
}
and point = {
x: int,
y: int
};
module Decode = {
let point = json =>
Json.Decode.{
x: json |> field("x", int),
y: json |> field("y", int)
};
let line = json =>
Json.Decode.{
start: json |> field("start", point),
end_: json |> field("end", point),
thickness: json |> optional(field("thickness", int))
};
};
let data = {| {
"start": { "x": 1, "y": -4 },
"end": { "x": 5, "y": 8 }
} |};
let line = data |> Json.parseOrRaise
|> Decode.line;
type line = {
start: point;
end_: point;
thickness: int option;}
and point = {
x: int;
y: int;}
module Decode =
struct
let point json =
let open Json.Decode in
{ x = (json |> (field "x" int)); y = (json |> (field "y" int)) }
let line json =
let open Json.Decode in
{
start = (json |> (field "start" point));
end_ = (json |> (field "end" point));
thickness = (json |> (optional (field "thickness" int)))
}
end
let data =
{| {
"start": { "x": 1, "y": -4 },
"end": { "x": 5, "y": 8 }
} |}
let line = (data |> Json.parseOrRaise) |> Decode.line
NOTE: Json.Decode.{ ... }
creates an ordinary record, but also opens the Json.Decode
module locally, within the
scope delimited by the curly braces, so we don't have to qualify the functions we use from it, like field
, int
and
optional
here. You can also use Json.Decode.( ... )
to open the module locally within the parentheses, if you're not
creating a record.
See examples for more.
npm install --save @glennsl/bs-json
Then add @glennsl/bs-json
to bs-dependencies
in your bsconfig.json
:
{
...
"bs-dependencies": ["@glennsl/bs-json"]
}
For the moment, please see the interface files:
If you look at the type signature of Js.Decode.array
, for example, you'll see it takes an 'a decoder
and returns an
'a array decoder
. 'a decoder
is just an alias for Js.Json.t -> 'a
, so if we expand the type signature of array
we'll get (Js.Json.t -> 'a) -> Js.Json.t -> 'a array
. We can now see that it is a function that takes a decoder and
returns a function, itself a decoder. Applying the int
decoder to array
will give us an int array decoder
, a
function Js.Json.t -> int array
.
If you've written a function that takes just Js.Json.t
and returns user-defined types of your own, you've already been
writing composable decoders! Let's look at Decode.point
from the example above:
let point = json => {
open! Json.Decode;
{
x: json |> field("x", int),
y: json |> field("y", int)
};
};
let point json =
let open! Json.Decode in
{ x = (json |> (field "x" int)); y = (json |> (field "y" int)) }
This is a function Js.Json.t -> point
, or a point decoder
. So if we'd like to decode an array of points, we can just
pass it to Json.Decode.array
to get a point array decoder
in return.
To write a decoder builder like Json.Decode.array
we need to take another decoder as an argument, and thanks to
currying we just need to apply it where we'd otherwise use a fixed decoder. Say we want to be able to decode both
int point
s and float point
s. First we'd have to parameterize the type:
type point('a) = {
x: 'a,
y: 'a
}
type 'a point = {
x: 'a;
y: 'a;}
Then we can change our point
function from above to take and use a decoder argument:
let point = (decodeNumber, json) => {
open! Json.Decode;
{
x: json |> field("x", decodeNumber),
y: json |> field("y", decodeNumber)
};
};
let point decodeNumber json =
let open! Json.Decode in
{
x = (json |> (field "x" decodeNumber));
y = (json |> (field "y" decodeNumber))
}
And if we wish we can now create aliases for each variant:
let intPoint = point(Json.Decode.int);
let floatPoint = point(Json.Decode.float);
let intPoint = point Json.Decode.int
let floatPoint = point Json.Decode.float
Encoders work exactly the same way, just in reverse. 'a encoder
is just an alias for 'a -> Js.Json.t
, and this also
transfers to composition: 'a encoder -> 'a array encoder
expands to ('a -> Js.Json.t) -> 'a array -> Js.Json.t
.
This work is dual-licensed under LGPL 3.0 and MPL 2.0. You can choose between one of them if you use this work.
Please see LICENSE.LGPL-3.0 and LICENSE.MPL-2.0 for the full text of each license.
SPDX-License-Identifier: LGPL-3.0 OR MPL-2.0
Json.Decode.id
arrayOf
encoderdict
encoder to jsonDict
dict
encoder that takes an additional encoder argument used to encode the contained values, and so it's consistent with the respective dict
decoder.bs-platform
peer dependency to 5.0.4 to stop the compiler's complaining.Js.Date.toJSON
with Js.Date.toJSONUsafe
, which is exactly the same, just to avoid deprecation warnings for end users (Thanks Bob!)bs-platform
>= 4.0.2Json.Decode.boolean
, Json.Encode.boolean
, Json.Encode.booleanArray
bs-platform
>= 3.0.0Json.Decode.boolean
, Json.Encode.boolean
, Json.Encode.booleanArray
Json.Encode.boolArray
Json.Encode.char
and Json.Decode.char
reasonml-community/bs-json
to glennsl/bs-json
bs-json
to @glennsl/bs-json
Json.Encoder.array
with Json.Encode.arrayOf
renamed to array
. Deprecated arrayOf
alias.Json.parse
, Json.parseOrRaise
, Json.stringify
date
encoder and decodertuple2
/tuple3
/tuple4
encoders and decodersJson.Encode.bool
Json.Encode.pair
Json.Encode.withDefault
Json.Encode.nullable
Json.Encode.arrayOf
Json.Encode.jsonArray
as replacement for Json.Encode.array
Json.Encode.array
Json.Decode.pair
Json.Encode.list
Json.Encode.object_
to Json.Encode.dict
Json.Encode.object_
taking a list of properties instead of a Json.Dict.t as before