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")
}
Search Document