std.json

JSON values, parsing, printing, and typed codecs.

Importuse std.json

Types

enum Json#

A parsed JSON value.

use std.json.Json

fn main() {
    match std.json.parse("[1, true]").unwrap() {
        Json.Arr(items) => println(items.len()), // 2
        _ => println("not an array"),
    }
}
Null

The JSON literal null.

Bool(bool)

A true or false literal.

Int(int)

A number that parsed without a fractional or exponent part.

Float(float)

A number that carried a fraction or an exponent.

Str(string)

A string, with escapes already decoded.

Arr([Json])

An array, in document order.

Obj({string: Json})

An object, in first-occurrence key order.

interface ToJson#

"This type knows how to become JSON."

The contract that makes the constructor wrapper disappear — aeson's ToJSON, serde's Into<Value>. A value inside a JSON literal is lifted through this, so {"total": job.count("total")} needs no Json.Int around something the checker already knows is an int.

Implemented here for the builtin scalars and for Json itself. A struct or enum the reflective codec accepts is lifted through to_value<T> instead, so declaring a codec is all a domain type needs — there is no second obligation to implement this by hand.

fn to_json(self: string) -> Json#

Converts a value to its JSON representation.

struct FieldRule#

An immutable JSON codec rule from json.field / json.rename_all, refined by rename, skip, omit_if_none and flatten.

Every field is private: a rule is ordinary immutable data that only std.json reads, and only std.json can produce one.

_field: string

The declared field this rule is about; empty for a whole-struct rule.

_wire: string

The name the field takes on the wire.

_skip: bool

Leave the field out of both directions entirely.

_style: string

A rename_all style applied to every field of the struct.

_flatten: bool

Splice the field's own object into this one.

_deny_unknown_fields: bool

Reject object keys no field claims while decoding.

struct VariantRule#

An immutable JSON enum-variant codec rule from json.variant, refined by rename and alias. Private fields, on the same terms as FieldRule.

_variant: string

The declared variant this rule is about.

_wire: string

The tag the variant takes on the wire.

struct EnumRepresentation#

A JSON enum representation declaration — one of externally_tagged, internally_tagged, adjacently_tagged, untagged.

_kind: string

The representation's own name, as the constructor spelled it.

struct JsonCodec#

A registered JSON codec declaration from json.codec<T>.

_type: string

The runtime name of the type the codec was declared for.

struct JsonOptions#

Serialization options for json.to_string_with.

sort_keys: bool
escape_html: bool
integral_floats: bool

Functions

extern fn parse(text: string) -> Result<Json, Error>#

Parses one RFC 8259 JSON value. Object insertion order is retained; duplicate keys are last-value-wins in first-occurrence position.

  • text: The document to read; it must hold exactly one JSON value.
let doc = std.json.parse("{\"name\":\"moon\",\"year\":2026}").unwrap()
println(std.json.keys(doc)) // ["name", "year"]
println(std.json.as_str(std.json.get(doc, "name").unwrap()).unwrap()) // moon
extern fn decode<T>(text: string) -> Result<T, Error>#

parse then from_value<T>, with one error channel for both halves.

  • text: The document to read and project onto T.
struct Project { name: string, year: int }

fn main() {
    let p = std.json.decode<Project>("{\"name\":\"moon\",\"year\":2026}").unwrap()
    println(p.name) // moon
    match std.json.decode<Project>("{\"name\":\"moon\",\"year\":\"soon\"}") {
        ok(_) => {},
        err(e) => println(e.message), // decode: expected int at $.year, found string
    }
}
extern fn decode_with<T>(text: string, defaults: T) -> Result<T, Error>#

decode with an explicit default-overlay value for missing struct fields.

struct Config { host: string, port: int }

fn main() {
    let defaults = Config { host: "localhost", port: 8080 }
    let cfg = std.json.decode_with<Config>("{\"port\":9000}", defaults).unwrap()
    println(cfg.host) // localhost
    println(cfg.port) // 9000
    // from_value_with does the same for an already-parsed document
}
extern fn from_value<T>(value: Json) -> Result<T, Error>#

Builds a T out of an already-parsed document, field by field, from the program's own type metadata. Supported shapes: int, float, bool, string, Json itself, Optional<T> (absent or null), [T], {string: T}, nested and flattened structs, and enums using their declared external, internal, adjacent, or untagged representation. Struct codecs may reject unknown fields; enum variants may have one canonical encode spelling plus decode-only aliases. A failure names the path it happened at: decode: expected string at $.tags[3].name, found int.

  • value: An already-parsed document to project onto T.
struct Point { x: int, y: int }

fn main() {
    let doc = std.json.parse("{\"x\":3,\"y\":4}").unwrap()
    let p = std.json.from_value<Point>(doc).unwrap()
    println(p.x + p.y) // 7
}
extern fn from_value_with<T>(value: Json, defaults: T) -> Result<T, Error>#

Like from_value, but missing struct fields retain the corresponding values from defaults. Present fields, including explicit null, decode normally; nested structs inherit their matching nested default value.

extern fn encode<T>(value: T) -> string#

A T rendered as compact JSON text.

Compiled from the program's codec metadata into a direct destination writer; it does not materialize an intermediate Json tree.

Total, for the same reason to_value is: every way an encode could fail is a property of T, and the checker decides it where T is written.

  • value: The value to render.
struct Project { name: string, year: int }

fn main() {
    let wire = std.json.encode<Project>(Project { name: "moon", year: 2026 })
    println(wire) // {"name":"moon","year":2026}
}
extern fn to_value<T>(value: T) -> Json#

The reverse walk: a T rendered as a Json tree. Struct fields keep declaration order, which dictionaries preserve.

Total: every way an encode could fail — an unsupported type, a dictionary that is not string-keyed, or an enum payload without a total declared representation — is a property of T, and the checker decides it where T is written. So there is no error channel to thread through a call site for a case that cannot arise; a T this rejects is a compile error naming the offending part of the type.

  • value: The value to walk into a Json tree.
struct Point { x: int, y: int }

fn main() {
    let tree = std.json.to_value<Point>(Point { x: 3, y: 4 })
    println(std.json.to_string(tree)) // {"x":3,"y":4}
    println(std.json.get(tree, "x").unwrap()) // Json.Int(3)
}
extern fn codec<T>(rules: [FieldRule]) -> JsonCodec#

Registers rules for T, for every decode/encode in the program — including T reached as a nested field. The rules are validated where they are declared: an unknown field, a duplicated field or wire name, an unknown style, or a skipped non-optional field is a compile error.

  • rules: The per-field rules, each built from field(...) or rename_all(...).
extern fn enum_codec<T>(representation: EnumRepresentation, rules: [VariantRule]) -> JsonCodec#

Registers an enum representation and per-variant spelling rules for T.

extern fn field(name: string) -> FieldRule#

Names one struct field in a codec declaration. Chain .rename, .skip, .omit_if_none, or .flatten.

  • name: The struct field the rule applies to, by its moon name.
struct Video { id: int, view_count: int }

fn main() {
    std.json.codec<Video>([
        std.json.field("id").rename("videoId"),
        std.json.field("view_count").rename("views"),
    ])
    println(std.json.encode<Video>(Video { id: 7, view_count: 41 }))
    // {"videoId":7,"views":41}
}
extern fn rename(self: FieldRule, wire: string) -> FieldRule#

The wire spelling of the named field.

  • wire: The name this field takes on the wire, encoding and decoding alike.
extern fn skip(self: FieldRule) -> FieldRule#

Leaves the named field off the wire entirely. A skipped field must be Optional<T>, since decoding cannot invent a value for it.

extern fn omit_if_none(self: FieldRule) -> FieldRule#

Leaves the named field off the wire *when it is none*, and writes it normally otherwise — serde's skip_serializing_if, Go's omitempty.

This is the distinction JSON draws and skip cannot: "lastBuiltAt": null is a present field holding null, and an absent "error" is a different wire fact. Encode-only; decoding already reads an absent field as none, which is what makes it round-trip. The field must be Optional<T>.

extern fn flatten(self: FieldRule) -> FieldRule#

Makes this nested nominal struct share its parent's object. Flatten cannot be combined with rename, skip, or omit_if_none.

extern fn deny_unknown_fields() -> FieldRule#

Rejects object keys not claimed by this struct's effective field set.

extern fn rename_all(style: string) -> FieldRule#

A default spelling for every field the declaration does not name. Styles: snake_case (the identity default), camelCase, PascalCase, kebab-case, SCREAMING_SNAKE_CASE.

  • style: One of snake_case, camelCase, PascalCase, kebab-case, or SCREAMING_SNAKE_CASE.
extern fn variant(name: string) -> VariantRule#

Names one enum variant in an enum codec declaration.

extern fn alias(self: VariantRule, wire: string) -> VariantRule#

Adds one decode-only spelling for the named variant.

extern fn externally_tagged() -> EnumRepresentation#

The default serde-style enum representation: strings for unit variants and one-key objects for payload variants.

extern fn internally_tagged(tag: string) -> EnumRepresentation#

An object representation whose tag lives beside fields of a struct payload.

extern fn adjacently_tagged(tag: string, content: string) -> EnumRepresentation#

An object representation with separate tag and payload-content keys.

extern fn untagged() -> EnumRepresentation#

A representation selected solely from each variant payload's JSON shape.

fn to_string(value: Json) -> string#

Compact RFC 8259 serialization.

  • value: The tree to render.
fn to_string_with(value: Json, opts: JsonOptions) -> string#

Compact serialization with explicit key ordering, escape profile, and whole float rendering. to_string is this with every switch off, and keeps its own measured code path.

  • value: The tree to render.
  • opts: Key ordering, escape profile, and whole-float rendering.
fn to_pretty(value: Json, indent: int) -> string#

Pretty serialization with indent spaces per nesting level.

  • value: The tree to render.
  • indent: Spaces of indentation per nesting level.
fn get(value: Json, key: string) -> Optional<Json>#

Returns an object field, or none for another shape or missing key.

  • value: The tree to read; anything but an object yields none.
  • key: The field name to look up.
fn at(value: Json, index: int) -> Optional<Json>#

Returns an array element, or none for another shape or invalid index.

  • value: The tree to read; anything but an array yields none.
  • index: Zero-based position; out-of-range yields none.
fn keys(value: Json) -> [string]#

Returns an object's keys, or an empty array for another shape.

  • value: The tree to read; anything but an object yields an empty array.
fn length(value: Json) -> int#

Returns the length of an array or object.

  • value: The tree to measure; anything but an array or object yields 0.
fn as_int(value: Json) -> Optional<int>#

Returns the integer payload when the value has that shape.

  • value: The tree to read.
fn as_float(value: Json) -> Optional<float>#

Returns the numeric payload as a float when possible.

  • value: The tree to read; an Int widens to a float.
fn as_str(value: Json) -> Optional<string>#

Returns the string payload when the value has that shape.

  • value: The tree to read.
fn as_bool(value: Json) -> Optional<bool>#

Returns the Boolean payload when the value has that shape.

  • value: The tree to read.
fn is_null(value: Json) -> bool#

Reports whether the value is JSON null.

  • value: The tree to test.
fn obj(pairs: [(string, Json)]) -> Json#

An object literal that keeps the given order: obj([("id", Json.Int(1))]). A repeated key is last-value-wins in first-occurrence position, which is what parse does with a duplicate too.

  • pairs: Key-value entries, kept in the order given.
fn obj_some(pairs: [(string, Optional<Json>)]) -> Json#

Builds an object from pairs, dropping the entries whose value is absent.

  • pairs: Key-value entries whose value may be absent; absent entries are left out of the object entirely.
fn arr(items: [Json]) -> Json#

An array literal: arr([Json.Int(1), Json.Int(2)]).

  • items: Elements, kept in the order given.
fn nullable_str(value: Optional<string>) -> Json#

some(v) as a value, absence as Json.Null — the shape every nullable database column takes on the wire.

  • value: The optional to lift; absence becomes Json.Null.
fn nullable_int(value: Optional<int>) -> Json#

Converts an optional integer to a JSON value.

  • value: The optional to lift; absence becomes Json.Null.
fn nullable_float(value: Optional<float>) -> Json#

Converts an optional float to a JSON value.

  • value: The optional to lift; absence becomes Json.Null.
fn nullable_bool(value: Optional<bool>) -> Json#

Converts an optional Boolean to a JSON value.

  • value: The optional to lift; absence becomes Json.Null.