gleamson
A pure-Gleam JSON library. A transparent value tree, combinator decoders that accumulate every error, and the very same behaviour on Erlang and JavaScript.
import gleamson import gleamson/decode pub type Cat { Cat(name: String, lives: Int) } let cat = { use name <- decode.field("name", decode.string) use lives <- decode.field("lives", decode.int) decode.success(Cat(name:, lives:)) } decode.from_string("{\"name\":\"Nono\",\"lives\":9}", cat) // -> Ok(Cat("Nono", 9))
Built the way Gleam wants it.
No FFI, no surprises. Just data you can see, decoders you can compose, and errors that tell you everything that went wrong.
Pure Gleam
One codebase that behaves identically on Erlang and JavaScript, with no Erlang/OTP version requirement.
Transparent values
Json is an ordinary type you can pattern‑match, walk and build.
Parse → encode round‑trips faithfully.
Errors accumulate
Decoders report every problem at once — each with a path like
["lives"] — not just the first.
Pretty & precise
to_string_pretty for humans, compact to_string for the
wire, and parse errors with exact byte positions.
Compose decoders
field, list, dict, optional,
one_of, then, enum — the familiar
use style.
Merge, Pointer & Patch
RFC 7386 merge, RFC 6901 JSON Pointer, and RFC 6902 JSON Patch with
apply and diff — built in.
Three small examples.
import gleamson.{Int, Null, Object, String} Object([ #("name", String("Lucy")), #("lives", Int(9)), #("flaws", Null), #("nicknames", gleamson.array(["Boo", "Bug"], of: String)), ]) |> gleamson.to_string // -> {"name":"Lucy","lives":9,"flaws":null,"nicknames":["Boo","Bug"]}
// Both fields are the wrong type — you get BOTH errors, with paths. decode.from_string("{\"name\": 42, \"lives\": \"nine\"}", cat) // -> Error(CouldNotDecode([ // DecodeError("String", "Int", ["name"]), // DecodeError("Int", "String", ["lives"]), // ])) // Want just the first instead? Use run_first. decode.run_first(value, cat) // -> Error(DecodeError("String", "Int", ["name"]))
import gleamson/patch.{Add, Replace} let assert Ok(doc) = gleamson.parse("{\"a\":1,\"b\":[10]}") // apply — atomic: all ops succeed, or none are applied patch.apply(doc, [Replace("/a", Int(2)), Add("/b/-", Int(20))]) // -> Ok({"a":2,"b":[10,20]}) // diff two documents into a patch you can send over the wire let ops = patch.diff(from: doc, to: updated)
Honest JSON, start to finish.
Transparent values, accumulating errors, and one behaviour across every target. Add it in one line.