JSON

Strict JSON decoding, typed decode, mutable DOM values, token input, and streaming output in std.json.

std.json provides strict JSON decoding, typed decoding for selected Zynx types, mutable DOM values, a token decoder for io.Reader inputs, one-shot encoding, and a small streaming output encoder.

In-tree tools using this surface include examples/manifest-inspect, examples/jsoncheck, and examples/jsonset.

The implementation currently uses a vendored JSON backend internally. That backend is not part of the public API: callers own and pass json.Value values, not backend pointers or parser flags.

Contract

json.decode(text) accepts strict RFC-style JSON. Comments and trailing commas are rejected. Input must be valid UTF-8. Parsed strings are returned as Zynx String values, so decoded strings that violate the Zynx text invariant, such as embedded NUL bytes, report JsonError.Invalid when extracted.

json.decode<T>(text) decodes directly into typed Zynx values. Supported targets are bool, integer and floating scalar types, String, json.Value, nullable values, Array<T>, and structs marked with @derive(json). str is not a typed decode target; use owned String for decoded text.

JSON struct decoding is strict by default. Unknown object fields are rejected unless the struct is marked @json_allow_unknown.

json.Value is an owned mutable DOM handle. Object and array mutation changes the parent value. Previously returned child handles remain valid handles to the old value they referenced.

JSON numbers are validated by the parser and stored as JSON number text. Value.number() returns that text as a String. json.encode supports Value, str, String, bool, integer scalars through 64-bit and pointer-sized widths, f32, and f64. Floating-point NaN and infinity are rejected as JsonError.Invalid.

JsonError.Invalid reports malformed JSON, invalid JSON number text, unsupported non-finite floats, or invalid internal text extraction. JsonError.Type reports a value of the wrong JSON kind. JsonError.Missing reports a missing object key or out-of-range array/object index. JsonError.State reports invalid streaming encoder/decoder state or invalid DOM mutation shape. Allocation failure is reported as AllocError.

DOM Example

import std.json;

fn main() {
    let doc = try json.decode("\x7b\"name\":\"app\",\"deps\":[\"util\"]\x7d");
    let name = try doc.string("name");
    assert name.repr() == "app";

    let deps = try doc.array("deps");
    assert try deps.len() == 1;
    let first = try (try deps[0]).string();
    assert first.repr() == "util";

    let out = try json.object();
    try (out["name"] = "app");
    try (out["deps"] = &deps);
    assert (try json.encode(&out)).repr() == "\x7b\"name\":\"app\",\"deps\":[\"util\"]\x7d";
}

Expected output: none. The assertions pass silently.

Typed Decode

import std.json;
import {
    Array
} from std.collections.array;
import {
    String
} from std.string;

@derive(json)
struct Package {
    @json_key("packageName") name: String,
    version: u16 = 1,
    tags: Array<String>,
    @json_optional note: String,
}

fn main() {
    let pkg = try json.decode<Package>("\x7b\"packageName\":\"zynx\",\"tags\":[\"compiler\"]\x7d");

    assert pkg.name.str() == "zynx";
    assert pkg.version == 1;
    assert pkg.tags.length == 1;
    assert pkg.note.str() == "";
}

Expected output: none. The assertions pass silently.

Typed struct attributes:

AttributeTargetMeaning
@derive(json)structEnables typed JSON decoding for the struct.
@json_allow_unknownstructAllows unmapped object fields.
@json_key("name")fieldUses a different JSON object key.
@json_optionalfieldAllows the field to be absent and keeps the field default.
@json_skipfieldIgnores the field during typed decode.

Absent nullable fields default to null. Absent fields with explicit defaults use that default. Absent String and Array<T> fields marked @json_optional default to empty values.

Streaming Input

json.Decoder<R: io.Reader> reads a JSON document from an io.Reader and yields semantic json.Token values. The first implementation validates and walks a DOM internally; the token API is the public contract.

import std.bytes;
import std.io;
import std.json;

fn main() {
    let stream = try io.MemoryStream(bytes.Bytes("\x7b\"ok\":true\x7d"));
    let dec = try json.Decoder<io.MemoryStream>(&stream);

    match try dec.next() {
        .BeginObject => {},
        _ => { assert false; },
    }
    match try dec.next() {
        .Key(key) => { assert key.str() == "ok"; },
        _ => { assert false; },
    }
    match try dec.next() {
        .Bool(value) => { assert value; },
        _ => { assert false; },
    }
    match try dec.next() {
        .EndObject => {},
        _ => { assert false; },
    }
    match try dec.next() {
        .Eof => {},
        _ => { assert false; },
    }
    try dec.finish();
}

Expected output: none. The assertions pass silently.

Streaming Output

import std.io;
import std.json;
import { String } from std.string;

fn main() {
    let stream = io.MemoryStream();
    let enc = json.Encoder<io.MemoryStream>(&stream);

    try enc.begin_object();
    try enc.field("ok", true);
    try enc.key("items");
    try enc.begin_array();
    try enc.value(1);
    try enc.value("two");
    try enc.end_array();
    try enc.end_object();
    try enc.finish();

    let text = try String(stream.bytes());
    assert text.repr() == "\x7b\"ok\":true,\"items\":[1,\"two\"]\x7d";
}

Expected output: none. The assertion passes silently.

API Reference

SymbolSignatureNotes
JsonErrorerrorInvalid, Type, Missing, and State.
ValuestructOwned mutable JSON DOM value handle.
TokenenumStreaming decoder token: BeginObject, EndObject, BeginArray, EndArray, Key(String), String(String), Number(String), Bool(bool), Null, or Eof.
Decoder<R: io.Reader>structToken decoder over a borrowed reader.
Encoder<W: io.Writer>structStreaming JSON output encoder over a borrowed writer.
decodedecode(text: str) throws(JsonError | AllocError) -> ValueDecodes strict JSON text into an owned mutable DOM value.
decode<T>decode<T>(text: str) throws(JsonError | AllocError) -> TDecodes into a supported scalar, String, json.Value, nullable value, Array<T>, or @derive(json) struct.
nullnull() throws(JsonError | AllocError) -> ValueCreates a JSON null value.
booleanboolean(value: bool) throws(JsonError | AllocError) -> ValueCreates a JSON boolean value.
stringstring(value: str) throws(JsonError | AllocError) -> ValueCreates a JSON string value.
numbernumber(value: str/integer/f32/f64) throws(JsonError | AllocError) -> ValueCreates a JSON number from validated text or scalar input.
arrayarray() throws(AllocError) -> ValueCreates an empty JSON array.
objectobject() throws(AllocError) -> ValueCreates an empty JSON object.
encodeencode(value) throws(JsonError | AllocError) -> StringEncodes a Value, text, bool, integer, f32, or f64 as strict JSON text.

Value Methods

MethodResult
kind()Internal kind code; prefer typed predicates and accessors.
is_null(), is_bool(), is_number(), is_string(), is_array(), is_object()Boolean kind checks.
object(), array()Retain and return this value when it has the requested kind.
string()Copy a JSON string into a Zynx String.
number()Copy a JSON number's validated source text into a String.
bool()Return a JSON boolean.
encode()Encode this value as JSON text.
get(key), [](key)Get an object member by key.
[](index), index(index)Get an array item by index.
object(key), array(key), string(key), number(key), bool(key)Get an object member and require the requested kind.
len()Return array length or object member count.
keys()Copy all object keys into an array.
key(index)Copy the object key at insertion order index.
put(key, value), push(value), set(index, value)Mutate objects and arrays with Value, text, bool, integer, f32, or f64 inputs.
[]=(key, value), []=(index, value)Indexed assignment forms for object members and array items.

Encoder Methods

MethodResult
begin_object(), end_object()Enter or leave an object context.
begin_array(), end_array()Enter or leave an array context.
key(key)Write an object key and colon; the next call must write the value.
value(value)Write a scalar or Value in the current context.
field(key, value)Write an object key and value pair.
finish()Verify that one complete document was written and all contexts are closed.

Decoder Methods

MethodResult
next()Return the next semantic json.Token.
skip_value()Consume the next complete value, including nested arrays or objects.
finish()Verify that the decoder reached Token.Eof.