Skip to content

std.json

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

The implementation uses vendored yyjson internally. That backend is not part of the public API: callers own and pass json.Value values, never 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 counted Zynx ^str values; a JSON \u0000 escape is preserved as ordinary U+0000 text.

json.decode<T>(text) decodes directly into typed Zynx values. It supports bool, integer and floating scalar types, str, json.Value, nullable values, and [T]. It does not decode arbitrary structs; use json.Value or Decoder when mapping JSON objects into domain types.

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.

RFC 0064 classifies the existing mutation names: if this mutable DOM surface is retained, put is the object key-to-value association operation and push is one array-tail insertion. It adds no insert/add aliases. This classification does not by itself ratify the full Value ownership, replacement, ordering, or failure contract; those semantics remain subject to a dedicated review.

JSON numbers are validated by the parser and stored as JSON number text. Value.number() returns that text as a str. json.encode supports Value, str, 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 state or invalid DOM mutation shape. Allocation failure is reported as AllocError.

Value, decoded str values, key arrays, and encoded output own their storage. Decoder borrows its io.Reader input while constructing the token stream; Encoder borrows its io.Writer target while writing. Object member ordering follows insertion order for the current DOM and token APIs, but the vendored backend, internal handles, memory layout, and parser flags are not public API.

Example

Zynx
import std.json;
import std.mem.{ AllocError };

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

	let deps = try doc.array("deps");
	assert try deps.length() == 1 as usize;
	let first = try (try deps.index(0)).string();
	assert first == "util";

	let out = try json.object();
	try out.put("name", "app");
	try out.put("deps", deps);
	assert (try json.encode(out)) == `{"name":"app","deps":["util"]}`;
}

Typed Decode

Zynx
import std.json;

fn main() {
	let count = try json.decode<u16>("12");
	let name = try json.decode<str>("\"zynx\"");
	let tags = try json.decode<[str]>(`["compiler","stdlib"]`);
	let missing = try json.decode<str?>("null");

	assert count == 12 as u16;
	assert name == "zynx";
	assert tags.length == 2 as usize;
	assert tags[0] == "compiler";
	assert missing == null;
}

Streaming Input

Decoder<R: io.Reader> reads a complete JSON document from an io.Reader and yields semantic tokens. It is not an incremental parser: construction reads the entire source into memory, validates it, builds the DOM, and then walks that DOM in insertion order. The token API is the public contract.

Zynx
import std.io;
import std.json;
import std.bytes.core.{ Bytes };

fn main() {
	var stream = try io.MemoryStream(Bytes(`{"ok":true}`));
	var dec = try json.Decoder<io.MemoryStream>(&stream);

	match try dec.next() {
		.BeginObject => {},
		_ => { assert false; },
	}
	match try dec.next() {
		.Key(key) => { assert key == "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();
}

Streaming Output

Streaming encoder objects and arrays are closure-scoped. The callback is called in place and can capture checked references to local data; the callback is not stored by the encoder. If the callback returns an error, the encoder returns that error and remains unfinished. Discard that encoder state instead of calling finish for output.

Zynx
import std.io;
import std.json;
import std.bytes;
import std.mem.{ AllocError };

fn main() {
	var stream = io.MemoryStream();
	var enc = json.Encoder<io.MemoryStream>(&stream);
	let count: u16 = 3;
	let first: i32 = 1;

	try enc.object((o: &json.ObjectScope<io.MemoryStream>) throws(json.JsonError | io.IOError | AllocError) {
		try o.field("name", "app");
		try o.field("count", count);
		try o.array("items", (a: &json.ArrayScope<io.MemoryStream>) throws(json.JsonError | io.IOError | AllocError) {
			try a.value(first);
			try a.value(false);
		});
	});
	try enc.finish();

	let produced = stream.bytes();
	let expected = bytes.Bytes(`{"name":"app","count":3,"items":[1,false]}`);
	assert produced.length == expected.length;
	var i: usize = 0;
	while i < produced.length {
		assert produced.at(i) == expected.at(i);
		i = i + 1;
	}
}

API Reference

Types

TypeKindPublic fieldsMethodsDescription
ValuestructNonekind, is_null, is_bool, is_number, is_string, is_array, is_object, object, array, string, number, bool, encode, get, length, index, keys, key, put, push, setOwned mutable JSON DOM value handle.
TokenenumN/AN/AStreaming decoder token: BeginObject, EndObject, BeginArray, EndArray, Key(str), String(str), Number(str), Bool(bool), Null, or Eof.
Decoder<R: io.Reader>structNonenext, skip_value, finishToken decoder over a borrowed io.Reader.
Encoder<W: io.Writer>structNoneobject, array, value, finishStreaming JSON output encoder over a borrowed io.Writer.
ObjectScope<W: io.Writer>structNonefield, object, arrayClosure-scoped object writer passed to Encoder.object and nested object scopes.
ArrayScope<W: io.Writer>structNonevalue, object, arrayClosure-scoped array writer passed to Encoder.array and nested array scopes.

Functions

NameSignatureParametersReturnsDescription
decodedecode(text: str) throws(JsonError | AllocError) -> ^Valuetext: strict JSON text^Value throws(JsonError | AllocError)Decodes text into an owned mutable JSON DOM value.
decode<T>decode<T>(text: str) throws(JsonError | AllocError) -> ^Ttext: strict JSON text^T throws(JsonError | AllocError)Decodes text into a supported scalar, ^str, ^json.Value, nullable value, or ^[^T].
nullnull() throws(JsonError | AllocError) -> ^Valuenone^ValueCreates a JSON null value.
booleanboolean(value: bool) throws(JsonError | AllocError) -> ^Valuevalue: boolean^ValueCreates a JSON boolean value.
stringstring(value: str) throws(JsonError | AllocError) -> ^Valuevalue: UTF-8 text^ValueCreates a JSON string value.
numbernumber(value: str) throws(JsonError | AllocError) -> ^Valuevalue: JSON number text^ValueCreates a JSON number from validated text.
numbernumber(value: integer/f32/f64) throws(JsonError | AllocError) -> ^Valuevalue: scalar number^ValueCreates a JSON number from a scalar. Integer overloads cover signed and unsigned widths through 64-bit plus isize/usize.
arrayarray() throws(AllocError) -> ^Valuenone^ValueCreates an empty JSON array.
objectobject() throws(AllocError) -> ^Valuenone^ValueCreates an empty JSON object.
encodeencode(value: Value) throws(JsonError | AllocError) -> ^strvalue: JSON DOM value^strEncodes a JSON DOM value.
encodeencode(value: str/bool/integer/f32/f64) throws(JsonError | AllocError) -> ^strvalue: scalar^strEncodes a scalar as strict JSON text. Integer overloads cover signed and unsigned widths through 64-bit plus isize/usize.

Value Methods

MethodSignatureDescription
kindkind() throws(JsonError) -> i32Returns the internal kind code. Prefer typed predicates and accessors.
is_nullis_null() -> boolReturns true for JSON null.
is_boolis_bool() -> boolReturns true for a JSON boolean.
is_numberis_number() -> boolReturns true for a JSON number.
is_stringis_string() -> boolReturns true for a JSON string.
is_arrayis_array() -> boolReturns true for a JSON array.
is_objectis_object() -> boolReturns true for a JSON object.
objectobject() throws(JsonError | AllocError) -> ^ValueRetains and returns this value when it is an object.
arrayarray() throws(JsonError | AllocError) -> ^ValueRetains and returns this value when it is an array.
stringstring() throws(JsonError | AllocError) -> ^strCopies a JSON string into an owning ^str.
numbernumber() throws(JsonError | AllocError) -> ^strCopies a JSON number's validated source text into an owned ^str.
boolbool() throws(JsonError) -> boolReturns a JSON boolean.
encodeencode() throws(JsonError | AllocError) -> ^strEncodes this value as JSON text.
getget(key: str) throws(JsonError | AllocError) -> ^ValueGets an object member by key.
objectobject(key: str) throws(JsonError | AllocError) -> ^ValueGets an object member and requires it to be an object.
arrayarray(key: str) throws(JsonError | AllocError) -> ^ValueGets an object member and requires it to be an array.
stringstring(key: str) throws(JsonError | AllocError) -> ^strGets an object member and requires it to be a string.
numbernumber(key: str) throws(JsonError | AllocError) -> ^strGets an object member and requires it to be a number, returned as validated source text.
boolbool(key: str) throws(JsonError | AllocError) -> boolGets an object member and requires it to be a boolean.
lengthlength() throws(JsonError) -> usizeReturns the logical array-item or object-member count. This remains a method because a non-container value reports JsonError.Type.
indexindex(index: usize) throws(JsonError | AllocError) -> ^ValueGets an array item by index.
keyskeys() throws(JsonError | AllocError) -> ^[^str]Copies all object keys into an array.
keykey(index: usize) throws(JsonError | AllocError) -> ^strCopies the object key at insertion order index.
putput(key: str, value) throws(JsonError | AllocError)Sets an object member. Supports Value, str, bool, integer scalars through 64-bit and pointer-sized widths, f32, and f64.
pushpush(value) throws(JsonError | AllocError)Appends an array item. Supports the same value set as put.
setset(index: usize, value) throws(JsonError | AllocError)Replaces an array item. Supports the same value set as put.

Encoder Methods

MethodSignatureDescription
objectobject<region L>(body: (&ObjectScope<W>) throws(JsonError | io.IOError | AllocError) -> void in L) throws(JsonError | io.IOError | AllocError)Writes a complete object and calls body with an object scope.
arrayarray<region L>(body: (&ArrayScope<W>) throws(JsonError | io.IOError | AllocError) -> void in L) throws(JsonError | io.IOError | AllocError)Writes a complete array and calls body with an array scope.
valuevalue(value) throws(JsonError | io.IOError | AllocError)Writes a complete scalar or Value as the root document.
finishfinish() throws(JsonError)Verifies that one complete document was written and all contexts are closed.

ObjectScope Methods

MethodSignatureDescription
fieldfield(key: str, value) throws(JsonError | io.IOError | AllocError)Writes an object key and scalar or Value value pair.
objectobject<region L>(key: str, body: (&ObjectScope<W>) throws(JsonError | io.IOError | AllocError) -> void in L) throws(JsonError | io.IOError | AllocError)Writes an object field whose value is a complete nested object.
arrayarray<region L>(key: str, body: (&ArrayScope<W>) throws(JsonError | io.IOError | AllocError) -> void in L) throws(JsonError | io.IOError | AllocError)Writes an object field whose value is a complete nested array.

ArrayScope Methods

MethodSignatureDescription
valuevalue(value) throws(JsonError | io.IOError | AllocError)Appends a scalar or Value to the array.
objectobject<region L>(body: (&ObjectScope<W>) throws(JsonError | io.IOError | AllocError) -> void in L) throws(JsonError | io.IOError | AllocError)Appends a complete nested object.
arrayarray<region L>(body: (&ArrayScope<W>) throws(JsonError | io.IOError | AllocError) -> void in L) throws(JsonError | io.IOError | AllocError)Appends a complete nested array.

Decoder Methods

MethodSignatureDescription
nextnext() throws(JsonError | AllocError) -> ^TokenReturns the next semantic token.
skip_valueskip_value() throws(JsonError | AllocError)Consumes the next complete value, including nested arrays or objects.
finishfinish() throws(JsonError)Verifies that the decoder reached Token.Eof.

Errors

ErrorMeaning
JsonError.InvalidMalformed JSON input, invalid JSON number text, unsupported float value, or JSON text that cannot be represented as Zynx text (str).
JsonError.TypeThe requested accessor or mutation does not match the value kind.
JsonError.MissingAn object key is absent or an index is out of range.
JsonError.StateStreaming encoder state is invalid or a DOM mutation shape is invalid.

Use std.io for reader and writer implementations, types and values for owned text, and std.bytes when binary input must be decoded before it can be parsed as JSON text.

Status

std.json is the current public strict JSON API. RFC 0062 accepts the target Value.length() spelling; the checked-in implementation remains transitional until the old abbreviation is removed without an alias. RFC 0064 classifies retained object put and array push names without freezing the complete DOM mutation contract. For the full exported surface, see Standard Library Inventory.

Released under the MIT License.