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
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
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.
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.
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
| Type | Kind | Public fields | Methods | Description |
|---|---|---|---|---|
Value | struct | None | kind, 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, set | Owned mutable JSON DOM value handle. |
Token | enum | N/A | N/A | Streaming decoder token: BeginObject, EndObject, BeginArray, EndArray, Key(str), String(str), Number(str), Bool(bool), Null, or Eof. |
Decoder<R: io.Reader> | struct | None | next, skip_value, finish | Token decoder over a borrowed io.Reader. |
Encoder<W: io.Writer> | struct | None | object, array, value, finish | Streaming JSON output encoder over a borrowed io.Writer. |
ObjectScope<W: io.Writer> | struct | None | field, object, array | Closure-scoped object writer passed to Encoder.object and nested object scopes. |
ArrayScope<W: io.Writer> | struct | None | value, object, array | Closure-scoped array writer passed to Encoder.array and nested array scopes. |
Functions
| Name | Signature | Parameters | Returns | Description |
|---|---|---|---|---|
decode | decode(text: str) throws(JsonError | AllocError) -> ^Value | text: 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) -> ^T | text: strict JSON text | ^T throws(JsonError | AllocError) | Decodes text into a supported scalar, ^str, ^json.Value, nullable value, or ^[^T]. |
null | null() throws(JsonError | AllocError) -> ^Value | none | ^Value | Creates a JSON null value. |
boolean | boolean(value: bool) throws(JsonError | AllocError) -> ^Value | value: boolean | ^Value | Creates a JSON boolean value. |
string | string(value: str) throws(JsonError | AllocError) -> ^Value | value: UTF-8 text | ^Value | Creates a JSON string value. |
number | number(value: str) throws(JsonError | AllocError) -> ^Value | value: JSON number text | ^Value | Creates a JSON number from validated text. |
number | number(value: integer/f32/f64) throws(JsonError | AllocError) -> ^Value | value: scalar number | ^Value | Creates a JSON number from a scalar. Integer overloads cover signed and unsigned widths through 64-bit plus isize/usize. |
array | array() throws(AllocError) -> ^Value | none | ^Value | Creates an empty JSON array. |
object | object() throws(AllocError) -> ^Value | none | ^Value | Creates an empty JSON object. |
encode | encode(value: Value) throws(JsonError | AllocError) -> ^str | value: JSON DOM value | ^str | Encodes a JSON DOM value. |
encode | encode(value: str/bool/integer/f32/f64) throws(JsonError | AllocError) -> ^str | value: scalar | ^str | Encodes a scalar as strict JSON text. Integer overloads cover signed and unsigned widths through 64-bit plus isize/usize. |
Value Methods
| Method | Signature | Description |
|---|---|---|
kind | kind() throws(JsonError) -> i32 | Returns the internal kind code. Prefer typed predicates and accessors. |
is_null | is_null() -> bool | Returns true for JSON null. |
is_bool | is_bool() -> bool | Returns true for a JSON boolean. |
is_number | is_number() -> bool | Returns true for a JSON number. |
is_string | is_string() -> bool | Returns true for a JSON string. |
is_array | is_array() -> bool | Returns true for a JSON array. |
is_object | is_object() -> bool | Returns true for a JSON object. |
object | object() throws(JsonError | AllocError) -> ^Value | Retains and returns this value when it is an object. |
array | array() throws(JsonError | AllocError) -> ^Value | Retains and returns this value when it is an array. |
string | string() throws(JsonError | AllocError) -> ^str | Copies a JSON string into an owning ^str. |
number | number() throws(JsonError | AllocError) -> ^str | Copies a JSON number's validated source text into an owned ^str. |
bool | bool() throws(JsonError) -> bool | Returns a JSON boolean. |
encode | encode() throws(JsonError | AllocError) -> ^str | Encodes this value as JSON text. |
get | get(key: str) throws(JsonError | AllocError) -> ^Value | Gets an object member by key. |
object | object(key: str) throws(JsonError | AllocError) -> ^Value | Gets an object member and requires it to be an object. |
array | array(key: str) throws(JsonError | AllocError) -> ^Value | Gets an object member and requires it to be an array. |
string | string(key: str) throws(JsonError | AllocError) -> ^str | Gets an object member and requires it to be a string. |
number | number(key: str) throws(JsonError | AllocError) -> ^str | Gets an object member and requires it to be a number, returned as validated source text. |
bool | bool(key: str) throws(JsonError | AllocError) -> bool | Gets an object member and requires it to be a boolean. |
length | length() throws(JsonError) -> usize | Returns the logical array-item or object-member count. This remains a method because a non-container value reports JsonError.Type. |
index | index(index: usize) throws(JsonError | AllocError) -> ^Value | Gets an array item by index. |
keys | keys() throws(JsonError | AllocError) -> ^[^str] | Copies all object keys into an array. |
key | key(index: usize) throws(JsonError | AllocError) -> ^str | Copies the object key at insertion order index. |
put | put(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. |
push | push(value) throws(JsonError | AllocError) | Appends an array item. Supports the same value set as put. |
set | set(index: usize, value) throws(JsonError | AllocError) | Replaces an array item. Supports the same value set as put. |
Encoder Methods
| Method | Signature | Description |
|---|---|---|
object | object<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. |
array | array<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. |
value | value(value) throws(JsonError | io.IOError | AllocError) | Writes a complete scalar or Value as the root document. |
finish | finish() throws(JsonError) | Verifies that one complete document was written and all contexts are closed. |
ObjectScope Methods
| Method | Signature | Description |
|---|---|---|
field | field(key: str, value) throws(JsonError | io.IOError | AllocError) | Writes an object key and scalar or Value value pair. |
object | object<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. |
array | array<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
| Method | Signature | Description |
|---|---|---|
value | value(value) throws(JsonError | io.IOError | AllocError) | Appends a scalar or Value to the array. |
object | object<region L>(body: (&ObjectScope<W>) throws(JsonError | io.IOError | AllocError) -> void in L) throws(JsonError | io.IOError | AllocError) | Appends a complete nested object. |
array | array<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
| Method | Signature | Description |
|---|---|---|
next | next() throws(JsonError | AllocError) -> ^Token | Returns the next semantic token. |
skip_value | skip_value() throws(JsonError | AllocError) | Consumes the next complete value, including nested arrays or objects. |
finish | finish() throws(JsonError) | Verifies that the decoder reached Token.Eof. |
Errors
| Error | Meaning |
|---|---|
JsonError.Invalid | Malformed JSON input, invalid JSON number text, unsupported float value, or JSON text that cannot be represented as Zynx text (str). |
JsonError.Type | The requested accessor or mutation does not match the value kind. |
JsonError.Missing | An object key is absent or an index is out of range. |
JsonError.State | Streaming encoder state is invalid or a DOM mutation shape is invalid. |
Related Modules
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.