glon

Types

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