gleamson/decode
Combinator decoders that turn a gleamson.Json value into typed Gleam data.
Decoders here accumulate errors: when one field fails, decoding keeps going so you get every problem at once, not just the first. A decoder always produces a best-effort value alongside a list of errors, where failed parts are filled with a zero value; that value is discarded by the runners unless the error list is empty.
A Decoder(t) is just a function fn(Json) -> #(t, List(DecodeError)), so
you can write your own as a plain function. Records are built with use.
import gleamson
import gleamson/decode
pub type Cat {
Cat(name: String, lives: Int, nicknames: List(String))
}
pub fn cat_from_json(text: String) -> Result(Cat, decode.Error) {
let cat = {
use name <- decode.field("name", decode.string)
use lives <- decode.field("lives", decode.int)
use nicknames <- decode.field("nicknames", decode.list(decode.string))
decode.success(Cat(name:, lives:, nicknames:))
}
decode.from_string(text, cat)
}
Types
Why a value could not be decoded: what was expected, what was found, and the path to the offending value.
pub type DecodeError {
DecodeError(
expected: String,
found: String,
path: List(String),
)
}
Constructors
-
DecodeError(expected: String, found: String, path: List(String))
A decoder produces a best-effort value together with any errors found. An empty error list means success.
pub type Decoder(t) =
fn(gleamson.Json) -> #(t, List(DecodeError))
A failure when going straight from text to typed data.
pub type Error {
CouldNotParse(gleamson.ParseError)
CouldNotDecode(List(DecodeError))
}
Constructors
-
CouldNotParse(gleamson.ParseError)The bytes were not valid JSON.
-
CouldNotDecode(List(DecodeError))The JSON was valid but did not match the decoder. Holds every error.
Values
pub fn at(
path: List(String),
inner: fn(gleamson.Json) -> #(a, List(DecodeError)),
) -> fn(gleamson.Json) -> #(a, List(DecodeError))
Decode a value found by following a path of object keys.
pub fn bool(json: gleamson.Json) -> #(Bool, List(DecodeError))
pub fn dict(
of value_decoder: fn(gleamson.Json) -> #(v, List(DecodeError)),
) -> fn(gleamson.Json) -> #(
dict.Dict(String, v),
List(DecodeError),
)
Decode a JSON object into a Dict keyed by its string keys.
pub fn enum(
first: #(String, a),
or others: List(#(String, a)),
) -> fn(gleamson.Json) -> #(a, List(DecodeError))
Decode a JSON string by mapping it to a value from a fixed set, the way you’d decode an enum-like custom type. The first pair’s value doubles as the fallback used while accumulating errors.
pub type Side {
Buy
Sell
}
let side = enum(#("buy", Buy), or: [#("sell", Sell)])
pub fn failure(
zero: t,
expected: String,
) -> fn(gleamson.Json) -> #(t, List(DecodeError))
A decoder that always fails, reporting expected. zero is the value used
to keep accumulating in surrounding decoders.
pub fn field(
named name: String,
of field_decoder: fn(gleamson.Json) -> #(a, List(DecodeError)),
then next: fn(a) -> fn(gleamson.Json) -> #(
final,
List(DecodeError),
),
) -> fn(gleamson.Json) -> #(final, List(DecodeError))
Decode a field of an object, then continue with the rest of the record. A failing field does not stop the others from being checked.
pub fn float(json: gleamson.Json) -> #(Float, List(DecodeError))
Decodes a JSON number as a float, accepting integer literals too.
pub fn from_string(
from text: String,
using decoder: fn(gleamson.Json) -> #(t, List(DecodeError)),
) -> Result(t, Error)
Parse a string and decode it in one step, collecting all decode errors.
pub fn index(
at position: Int,
of inner: fn(gleamson.Json) -> #(a, List(DecodeError)),
) -> fn(gleamson.Json) -> #(a, List(DecodeError))
Decode the element at a given array index.
pub fn int(json: gleamson.Json) -> #(Int, List(DecodeError))
pub fn json(
value: gleamson.Json,
) -> #(gleamson.Json, List(DecodeError))
A decoder that accepts anything and hands back the raw Json.
pub fn list(
of inner: fn(gleamson.Json) -> #(a, List(DecodeError)),
) -> fn(gleamson.Json) -> #(List(a), List(DecodeError))
Decode a JSON array, applying inner to every element and collecting every
element’s errors.
pub fn map(
decoder: fn(gleamson.Json) -> #(a, List(DecodeError)),
with transform: fn(a) -> b,
) -> fn(gleamson.Json) -> #(b, List(DecodeError))
Transform a decoder’s value. Errors are carried through unchanged.
pub fn one_of(
first: fn(gleamson.Json) -> #(a, List(DecodeError)),
or others: List(fn(gleamson.Json) -> #(a, List(DecodeError))),
) -> fn(gleamson.Json) -> #(a, List(DecodeError))
Try first; if it fails, try each decoder in others in turn, returning
the first that succeeds. If none match, every branch’s errors are reported.
// a field that may arrive as an int or as a bool
one_of(int, [map(bool, fn(b) { case b { True -> 1 False -> 0 } })])
pub fn optional(
of inner: fn(gleamson.Json) -> #(a, List(DecodeError)),
) -> fn(gleamson.Json) -> #(option.Option(a), List(DecodeError))
Wrap a decoder so that null becomes None.
pub fn optional_field(
named name: String,
of field_decoder: fn(gleamson.Json) -> #(a, List(DecodeError)),
then next: fn(option.Option(a)) -> fn(gleamson.Json) -> #(
final,
List(DecodeError),
),
) -> fn(gleamson.Json) -> #(final, List(DecodeError))
Like field, but a missing key or null value yields None instead of
an error.
pub fn run(
json: gleamson.Json,
using decoder: fn(gleamson.Json) -> #(t, List(DecodeError)),
) -> Result(t, List(DecodeError))
Run a decoder, collecting all errors.
pub fn run_first(
json: gleamson.Json,
using decoder: fn(gleamson.Json) -> #(t, List(DecodeError)),
) -> Result(t, DecodeError)
Run a decoder but report only the first error. Handy when a single error is all you want to surface to the caller.
pub fn string(
json: gleamson.Json,
) -> #(String, List(DecodeError))
pub fn success(
value: t,
) -> fn(gleamson.Json) -> #(t, List(DecodeError))
A decoder that always succeeds with the given value. Used to finish a
use chain.
pub fn then(
decoder: fn(gleamson.Json) -> #(a, List(DecodeError)),
apply next: fn(a) -> fn(gleamson.Json) -> #(
b,
List(DecodeError),
),
) -> fn(gleamson.Json) -> #(b, List(DecodeError))
Decode a value, then use it to choose the next decoder. Useful for validation, or for discriminated unions (read a “type” field, then decode the matching shape). This short-circuits: if the first decoder fails, the chosen one is not run.
use n <- then(int)
case n >= 0 {
True -> success(n)
False -> failure(0, "a non-negative int")
}