glon
Types
JsonSchema
opaqueA combined JSON Schema and decoder for values of type t.
Each JsonSchema(t) carries both a schema definition (for generating
JSON Schema output) and a decoder (for parsing JSON into Gleam values),
keeping the two in sync by construction.
Build schemas using the primitive constructors (string, integer, etc.),
composites (array, nullable), and object builders (field, optional,
etc.), then use to_json, to_string, or decode to consume them.
pub opaque type JsonSchema(t)
Values
pub fn any_of(variants: List(JsonSchema(t))) -> JsonSchema(t)
A schema where the value must match at least one of the given sub-schemas.
Produces {"anyOf": [...]}. Behaves identically to one_of for decoding
(first match wins), but generates anyOf instead of oneOf in the schema.
The distinction matters for JSON Schema validation: oneOf requires exactly
one match, anyOf allows multiple.
Examples
let schema = glon.any_of([
glon.string() |> glon.map(TextVal),
glon.integer() |> glon.map(NumVal),
])
glon.to_string(schema)
// -> "{\"anyOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}"
pub fn array(of inner: JsonSchema(t)) -> JsonSchema(List(t))
A schema for JSON arrays where every element matches the given inner schema.
Produces {"type": "array", "items": <inner>} and decodes JSON arrays
into List(t).
Examples
let schema = glon.array(of: glon.string())
glon.to_string(schema)
// -> "{\"type\":\"array\",\"items\":{\"type\":\"string\"}}"
glon.decode(schema, from: "[\"a\",\"b\"]")
// -> Ok(["a", "b"])
pub fn boolean() -> JsonSchema(Bool)
A schema for JSON booleans.
Produces {"type": "boolean"} and decodes JSON booleans into Bool.
Examples
let schema = glon.boolean()
glon.to_string(schema)
// -> "{\"type\":\"boolean\"}"
glon.decode(schema, from: "true")
// -> Ok(True)
pub fn constant(value: String) -> JsonSchema(String)
A schema that accepts only a single specific string value.
Produces {"type": "string", "const": "<value>"} and decodes only
that exact string.
Examples
let schema = glon.constant("active")
glon.to_string(schema)
// -> "{\"type\":\"string\",\"const\":\"active\"}"
glon.decode(schema, from: "\"active\"")
// -> Ok("active")
glon.decode(schema, from: "\"inactive\"")
// -> Error(...)
pub fn constant_map(
value: String,
mapped mapped: t,
) -> JsonSchema(t)
Like constant, but maps the matched string to an arbitrary Gleam value.
Examples
let schema = glon.constant_map("yes", mapped: True)
glon.to_string(schema)
// -> "{\"type\":\"string\",\"const\":\"yes\"}"
glon.decode(schema, from: "\"yes\"")
// -> Ok(True)
pub fn decode(
schema: JsonSchema(t),
from json_string: String,
) -> Result(t, json.DecodeError)
Decode a JSON string using the schema’s decoder.
Parses the given JSON string and decodes it according to the schema.
Returns Ok(value) on success or Error(DecodeError) on failure.
Examples
let schema = glon.integer()
glon.decode(schema, from: "42")
// -> Ok(42)
glon.decode(schema, from: "\"not a number\"")
// -> Error(...)
pub fn describe(
schema: JsonSchema(t),
description description: String,
) -> JsonSchema(t)
Attach a "description" annotation to any schema.
The description appears in the JSON Schema output but does not affect decoding. Can be composed with other annotations and combinators.
Examples
let schema =
glon.string()
|> glon.describe("A person's full name")
glon.to_string(schema)
// -> "{\"type\":\"string\",\"description\":\"A person's full name\"}"
pub fn enum(values: List(String)) -> JsonSchema(String)
A schema that restricts values to a fixed set of strings.
Produces {"type": "string", "enum": [...]} and decodes only strings
that appear in the given list. Decoding rejects any value not in the list.
Examples
let schema = glon.enum(["red", "green", "blue"])
glon.to_string(schema)
// -> "{\"type\":\"string\",\"enum\":[\"red\",\"green\",\"blue\"]}"
glon.decode(schema, from: "\"red\"")
// -> Ok("red")
glon.decode(schema, from: "\"yellow\"")
// -> Error(...)
pub fn enum_map(variants: List(#(String, t))) -> JsonSchema(t)
Like enum, but maps each string value to an arbitrary Gleam type.
Takes a list of #(json_string, gleam_value) pairs. The schema output
uses the JSON strings, while the decoder maps each to its paired value.
Examples
let schema = glon.enum_map([
#("red", Red), #("green", Green), #("blue", Blue),
])
glon.to_string(schema)
// -> "{\"type\":\"string\",\"enum\":[\"red\",\"green\",\"blue\"]}"
glon.decode(schema, from: "\"red\"")
// -> Ok(Red)
pub fn field(
named name: String,
of schema: JsonSchema(a),
next next: fn(a) -> JsonSchema(b),
) -> JsonSchema(b)
Declare a required object field.
The field appears in the schema’s "properties" and "required" array.
Decoding fails if the field is missing from the JSON input.
Used with Gleam’s use syntax to chain multiple fields together.
Examples
let schema = {
use name <- glon.field("name", glon.string())
use age <- glon.field("age", glon.integer())
glon.success(User(name:, age:))
}
glon.decode(schema, from: "{\"name\":\"Alice\",\"age\":30}")
// -> Ok(User(name: "Alice", age: 30))
pub fn field_with_default(
named name: String,
of schema: JsonSchema(a),
default default_value: a,
encode encode: fn(a) -> json.Json,
next next: fn(a) -> JsonSchema(b),
) -> JsonSchema(b)
Declare an optional object field with a default value.
The field appears in the schema’s "properties" with a "default"
annotation, but not in "required". When the field is absent from the
JSON input, the decoder uses the provided default value. Unlike optional,
the continuation receives the unwrapped type a directly, not Option(a).
The encode parameter converts the default value to json.Json for the
schema output. For primitives, use json.int, json.string, json.float,
or json.bool.
Examples
let schema = {
use host <- glon.field("host", glon.string())
use port <- glon.field_with_default(
"port", glon.integer(),
default: 8080, encode: json.int,
)
glon.success(Config(host:, port:))
}
glon.to_string(schema)
// -> "{...\"port\":{\"type\":\"integer\",\"default\":8080}...}"
glon.decode(schema, from: "{\"host\":\"localhost\"}")
// -> Ok(Config(host: "localhost", port: 8080))
glon.decode(schema, from: "{\"host\":\"localhost\",\"port\":3000}")
// -> Ok(Config(host: "localhost", port: 3000))
pub fn integer() -> JsonSchema(Int)
A schema for JSON integers.
Produces {"type": "integer"} and decodes JSON integers into Int.
Examples
let schema = glon.integer()
glon.to_string(schema)
// -> "{\"type\":\"integer\"}"
glon.decode(schema, from: "42")
// -> Ok(42)
pub fn map(
schema: JsonSchema(a),
with transform: fn(a) -> b,
) -> JsonSchema(b)
Transform the decoded type of a schema without changing its JSON Schema output.
The schema definition stays the same, but the decoder maps decoded values
through the given function. Useful for wrapping primitives in custom types
or making different schemas produce the same type for use with one_of.
Examples
type Email { Email(String) }
let schema = glon.string() |> glon.map(Email)
glon.to_string(schema)
// -> "{\"type\":\"string\"}"
glon.decode(schema, from: "\"a@b.com\"")
// -> Ok(Email("a@b.com"))
pub fn nullable(
inner: JsonSchema(t),
) -> JsonSchema(option.Option(t))
A schema for values that may be null.
Wraps an inner schema to also accept JSON null. Produces a schema with
"type": ["<inner_type>", "null"] for simple inner types, or an anyOf
for complex ones. Decodes into Option(t).
Examples
let schema = glon.nullable(glon.string())
glon.to_string(schema)
// -> "{\"type\":[\"string\",\"null\"]}"
glon.decode(schema, from: "\"hello\"")
// -> Ok(Some("hello"))
glon.decode(schema, from: "null")
// -> Ok(None)
pub fn number() -> JsonSchema(Float)
A schema for JSON numbers (floating point).
Produces {"type": "number"} and decodes JSON numbers into Float.
Examples
let schema = glon.number()
glon.to_string(schema)
// -> "{\"type\":\"number\"}"
glon.decode(schema, from: "3.14")
// -> Ok(3.14)
pub fn one_of(variants: List(JsonSchema(t))) -> JsonSchema(t)
A schema where the value must match exactly one of the given sub-schemas.
Produces {"oneOf": [...]}. The decoder tries each variant in order and
returns the first successful match. All variants must decode to the same
Gleam type t — use map to align types if needed.
Examples
type Value { TextVal(String) NumVal(Int) }
let schema = glon.one_of([
glon.string() |> glon.map(TextVal),
glon.integer() |> glon.map(NumVal),
])
glon.to_string(schema)
// -> "{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}"
pub fn optional(
named name: String,
of schema: JsonSchema(a),
next next: fn(option.Option(a)) -> JsonSchema(b),
) -> JsonSchema(b)
Declare an optional object field.
The field appears in the schema’s "properties" but not in "required".
The continuation receives Option(a): Some(value) when the field is
present, None when absent.
Examples
let schema = {
use name <- glon.field("name", glon.string())
use email <- glon.optional("email", glon.string())
glon.success(User(name:, email:))
}
glon.decode(schema, from: "{\"name\":\"Alice\"}")
// -> Ok(User(name: "Alice", email: None))
pub fn optional_or_null(
named name: String,
of schema: JsonSchema(a),
next next: fn(option.Option(a)) -> JsonSchema(b),
) -> JsonSchema(b)
Declare an optional object field that may also be null.
Like optional, but the schema type is wrapped with nullable and the
decoder treats both an absent field and a null value as None.
Examples
let schema = {
use name <- glon.field("name", glon.string())
use nick <- glon.optional_or_null("nickname", glon.string())
glon.success(User(name:, nickname: nick))
}
// Field absent -> None, field null -> None, field present -> Some(value)
pub fn string() -> JsonSchema(String)
A schema for JSON strings.
Produces {"type": "string"} and decodes JSON strings into String.
Examples
let schema = glon.string()
glon.to_string(schema)
// -> "{\"type\":\"string\"}"
glon.decode(schema, from: "\"hello\"")
// -> Ok("hello")
pub fn success(value: t) -> JsonSchema(t)
Finish building an object schema by providing the final value.
This is used as the last step in a chain of field, optional, or
field_with_default calls via use syntax. It produces an empty object
node that gets merged with the fields collected from the chain.
Examples
let schema = {
use name <- glon.field("name", glon.string())
use age <- glon.field("age", glon.integer())
glon.success(User(name:, age:))
}
pub fn tagged_union(
discriminator discriminator: String,
variants variants: List(#(String, JsonSchema(t))),
) -> JsonSchema(t)
A schema for discriminated unions (tagged unions).
Produces a oneOf schema where each variant is an object with a
discriminator field set to a constant tag value. The decoder checks
the discriminator field to select the correct variant.
Examples
type Shape { Circle(Float) Square(Float) }
let schema = glon.tagged_union("type", [
#("circle", {
use radius <- glon.field("radius", glon.number())
glon.success(Circle(radius))
}),
#("square", {
use side <- glon.field("side", glon.number())
glon.success(Square(side))
}),
])
pub fn to_json(schema: JsonSchema(t)) -> json.Json
Convert a schema to a json.Json value.
Returns the JSON Schema representation as a json.Json value that can
be further processed or serialized.
pub fn to_string(schema: JsonSchema(t)) -> String
Convert a schema to a JSON string.
Returns the JSON Schema as a serialized JSON string.
Examples
glon.string() |> glon.to_string
// -> "{\"type\":\"string\"}"