API Reference #
Complete API documentation for zig-json - a comprehensive JSON library for Zig 0.16.0.
Table of Contents #
- Core Types
- Parsing
- Encoding
- Unmarshaling
- Time Types
- JSON Schema Validation
- JSON Patch (RFC 6902)
- JSON Lines (JSONL)
- JSONPath Query
- Schema Generation
- Streaming Tokenizer
- SIMD Primitives
- Async Parsing
- JSON-LD
- CBOR Encoding
- Options Reference
Core Types #
Value #
The fundamental JSON value type:
pub const Value = union(enum) {
null,
bool: bool,
num: Number,
str: []const u8,
arr: []Value,
obj: []Member,
pub fn eql(a: Value, b: Value) bool;
};
Member #
Key-value pair in JSON objects:
pub const Member = struct {
k: []const u8, // Key
v: Value, // Value
};
Number #
Normalized base-10 decimal representation preserving exact precision:
pub const Number = struct {
lexeme: []const u8, // Original string representation
sign: i8, // 1 or -1
digits: []const u8, // Normalized digit string
exp10: i32, // Base-10 exponent
pub fn isInteger(self: Number) bool;
pub fn compare(a: Number, b: Number) i32;
pub fn isMultipleOf(self: Number, divisor: Number) bool;
};
Document #
Parsed JSON document with arena allocator:
pub const Document = struct {
arena: std.heap.ArenaAllocator,
root: Value,
pub fn deinit(self: *Document) void;
};
Parsing #
parse #
pub fn parse(
allocator: std.mem.Allocator,
input: []const u8,
options: ParseOptions,
err: *ParseError
) !Document
Parse JSON input into a Document containing a Value tree.
Example:
var err = json.ParseError.init();
var doc = try json.parse(allocator, "{\"key\": \"value\"}", .{}, &err);
defer doc.deinit();
// Access the parsed value
for (doc.root.obj) |m| {
std.debug.print("{s}: {any}\n", .{m.k, m.v});
}
stringify #
pub fn stringify(allocator: std.mem.Allocator, value: Value) ![]u8
Convert a json.Value to minified JSON string.
debugJson #
pub fn debugJson(allocator: std.mem.Allocator, value: Value) ![]u8
Convert to JSON with sorted keys and escaped non-ASCII. Useful for reproducible test output.
Encoding #
encode #
pub fn encode(
allocator: std.mem.Allocator,
value: anytype,
options: EncodeOptions
) ![]u8
Encode any Zig value to a JSON string.
Supported Types:
| Zig Type | JSON Output |
|---|---|
bool |
true / false |
| integers | Number |
| floats | Number (NaN/Inf -> null by default) |
[]const u8 |
String |
?T |
Value or null |
enum |
String (tag name) |
struct |
Object |
| tuple | Array |
[]T, [N]T |
Array |
union(enum) |
{"tag": payload} |
Example:
const Point = struct { x: i32, y: i32 };
const p = Point{ .x = 10, .y = 20 };
const output = try json.encode(allocator, p, .{});
defer allocator.free(output);
// {"x":10,"y":20}
// With pretty printing
const pretty = try json.encode(allocator, p, .{ .indent = " " });
defer allocator.free(pretty);
encodeToWriter #
pub fn encodeToWriter(
writer: anytype,
value: anytype,
options: EncodeOptions
) !void
Encode directly to a writer (streaming):
var file = try std.fs.cwd().createFile("output.json", .{});
defer file.close();
try json.encodeToWriter(file.writer(), my_data, .{ .indent = " " });
toValue #
pub fn toValue(
allocator: std.mem.Allocator,
value: anytype,
options: EncodeOptions
) !Value
Convert any Zig value to a json.Value tree.
Struct Field Configuration #
Use json_config to customize field serialization:
const Person = struct {
first_name: []const u8,
last_name: []const u8,
internal_id: u64,
metadata: Metadata,
pub const json_config = .{
.first_name = FieldConfig{ .name = "firstName" },
.last_name = FieldConfig{ .name = "lastName" },
.internal_id = FieldConfig{ .skip = true },
.metadata = FieldConfig{ .@"inline" = true },
};
};
FieldConfig Options #
pub const FieldConfig = struct {
name: ?[]const u8 = null, // Custom JSON key name
skip: bool = false, // Skip this field entirely
omitempty: bool = false, // Omit if null/zero/empty
stringify: bool = false, // Encode numbers as strings
binary_format: ?BinaryFormat = null, // Encoding for []u8 fields
@"inline": bool = false, // Flatten nested struct fields
unknown: bool = false, // Capture unknown JSON members
};
Binary Formats #
pub const BinaryFormat = enum {
utf8, // Default - UTF-8 string
base64, // RFC 4648 standard Base64
base64url, // RFC 4648 URL-safe Base64
hex, // Lowercase hexadecimal
hex_upper, // Uppercase hexadecimal
base32, // RFC 4648 Base32
};
Custom Marshaler #
Types can implement jsonMarshal for custom serialization:
const Point = struct {
x: f64,
y: f64,
pub fn jsonMarshal(self: @This(), allocator: std.mem.Allocator) !json.Value {
var arr = try allocator.alloc(json.Value, 2);
arr[0] = try json.toValue(allocator, self.x, .{});
arr[1] = try json.toValue(allocator, self.y, .{});
return .{ .arr = arr };
}
};
// Encode with allocator for custom marshalers
const output = try json.encode(allocator, point, .{ .allocator = allocator });
Unmarshaling #
unmarshal #
pub fn unmarshal(
comptime T: type,
allocator: std.mem.Allocator,
input: []const u8
) UnmarshalError!T
Parse JSON and decode into a Zig type:
const Person = struct {
name: []const u8,
age: ?i32 = null,
};
const p = try json.unmarshal(Person, allocator, "{\"name\": \"Alice\", \"age\": 30}");
defer json.free(Person, allocator, &p);
std.debug.print("Name: {s}, Age: {d}\n", .{p.name, p.age.?});
unmarshalWithInfo #
Returns detailed error information with JSON Pointer paths:
pub fn unmarshalWithInfo(
comptime T: type,
allocator: std.mem.Allocator,
input: []const u8
) UnmarshalResult(T)
Example:
const result = json.unmarshalWithInfo(MyType, allocator, input);
switch (result) {
.ok => |value| {
// Use value
},
.err => |info| {
std.debug.print("Error at {s}: {s}\n", .{info.path, info.message});
},
}
fromValue #
Unmarshal from existing Value tree:
pub fn fromValue(
comptime T: type,
allocator: std.mem.Allocator,
value: Value
) UnmarshalError!T
free #
Free memory allocated during unmarshaling:
pub fn free(comptime T: type, allocator: std.mem.Allocator, value: *T) void
Custom Unmarshaler #
Types can implement jsonUnmarshal:
const Point = struct {
x: f64,
y: f64,
pub fn jsonUnmarshal(allocator: std.mem.Allocator, value: json.Value) !Point {
_ = allocator;
if (value != .arr or value.arr.len != 2) return error.InvalidFormat;
return .{
.x = std.fmt.parseFloat(f64, value.arr[0].num.lexeme) catch return error.InvalidFormat,
.y = std.fmt.parseFloat(f64, value.arr[1].num.lexeme) catch return error.InvalidFormat,
};
}
};
Time Types #
Timestamp #
RFC 3339 formatted timestamp:
const ts = json.Timestamp.fromUnix(1705314600);
// Convert to RFC 3339 string
const s = try ts.toRfc3339(allocator);
defer allocator.free(s);
// "2024-01-15T10:30:00Z"
// Parse from string
const parsed = try json.Timestamp.fromRfc3339("2024-01-15T10:30:00Z");
UnixTimestamp / UnixTimestampMs #
Unix timestamps as numbers:
const Response = struct {
created_at: json.UnixTimestamp,
updated_at: json.UnixTimestampMs,
};
// Encodes as: {"created_at":1705314600,"updated_at":1705314600000}
Duration #
ISO 8601 duration strings:
const d = json.Duration.fromSeconds(2 * 3600 + 30 * 60 + 15);
const s = try d.toIso8601(allocator);
defer allocator.free(s);
// "PT2H30M15S"
FormattedTimestamp #
Custom timestamp formats:
const SqlTimestamp = json.FormattedTimestamp(json.TimeFormat.sql_datetime);
const ts = SqlTimestamp.fromUnix(1705314600);
const s = try ts.format(allocator);
// "2024-01-15 10:30:00"
Available Formats:
TimeFormat.rfc3339-"%Y-%m-%dT%H:%M:%SZ"TimeFormat.rfc3339_ms-"%Y-%m-%dT%H:%M:%S.%fZ"TimeFormat.iso8601_date-"%Y-%m-%d"TimeFormat.us_date-"%m/%d/%Y"TimeFormat.eu_date-"%d/%m/%Y"TimeFormat.sql_datetime-"%Y-%m-%d %H:%M:%S"
JSON Schema Validation #
compileSchema #
pub fn compileSchema(
allocator: std.mem.Allocator,
doc: *const Document,
options: SchemaOptions,
resolver: ?Resolver,
err: *ParseError
) !CompiledSchema
validate #
pub fn validate(
allocator: std.mem.Allocator,
schema: *const CompiledSchema,
instance: Value,
options: SchemaOptions
) !ValidationReport
Example:
const schema_json =
\\{
\\ "type": "object",
\\ "required": ["name"],
\\ "properties": {
\\ "name": {"type": "string"},
\\ "age": {"type": "integer", "minimum": 0}
\\ }
\\}
;
var err = json.ParseError.init();
var schema_doc = try json.parse(allocator, schema_json, .{}, &err);
defer schema_doc.deinit();
var compiled = try json.compileSchema(allocator, &schema_doc, .{}, null, &err);
defer compiled.deinit();
const report = try json.validate(allocator, &compiled, instance, .{});
if (!report.ok) {
for (report.errors) |e| {
std.debug.print("Error at {s}: {s}\n", .{e.instance_ptr, e.message});
}
}
JSON Patch (RFC 6902) #
applyPatch #
pub fn applyPatch(
allocator: std.mem.Allocator,
doc: Value,
patch: []const PatchOperation
) PatchError!Value
parsePatch #
pub fn parsePatch(
allocator: std.mem.Allocator,
patch_doc: Value
) PatchError![]PatchOperation
diffPatch #
Generate a patch from two values:
pub fn diffPatch(
allocator: std.mem.Allocator,
from: Value,
to: Value
) PatchError![]PatchOperation
applyMergePatch #
RFC 7396 Merge Patch:
pub fn applyMergePatch(
allocator: std.mem.Allocator,
target: Value,
patch: Value
) PatchError!Value
Example:
const ops = [_]json.PatchOperation{
.{ .op = .add, .path = "/tags", .value = .{ .arr = &[_]json.Value{} } },
.{ .op = .add, .path = "/tags/-", .value = .{ .str = "new" } },
};
const result = try json.applyPatch(allocator, doc, &ops);
defer json.freePatchValue(allocator, result);
JSON Lines (JSONL) #
parseJsonl #
Parse entire JSONL document:
pub fn parseJsonl(
allocator: std.mem.Allocator,
input: []const u8,
options: JsonlOptions,
err_out: *JsonlError
) !Document
jsonlIterator #
Stream JSONL line by line:
pub fn jsonlIterator(
allocator: std.mem.Allocator,
input: []const u8,
options: JsonlOptions
) JsonlIterator
Example:
const input =
\\{"name": "Alice"}
\\{"name": "Bob"}
;
var iter = json.jsonlIterator(allocator, input, .{});
defer iter.deinit();
while (try iter.next()) |value| {
defer iter.freeValue(value);
// Process each line
}
JSONPath Query #
jsonpath #
pub fn jsonpath(
allocator: std.mem.Allocator,
root: Value,
path: []const u8
) PathError!QueryResult
Supported Syntax:
$- Root element.nameor['name']- Child element..- Recursive descent*- Wildcard (all children)[n]- Array index[start:end:step]- Array slice[?(@.field == value)]- Filter expression[a,b,c]or[0,1,2]- Union expressions
Example:
var result = try json.jsonpath(allocator, doc.root, "$.users[*].name");
defer result.deinit();
for (result.values) |name| {
std.debug.print("Name: {s}\n", .{name.str});
}
Schema Generation #
generateSchema #
Generate JSON Schema from a Zig type:
pub fn generateSchema(
comptime T: type,
allocator: std.mem.Allocator,
options: SchemaGenOptions
) !Value
Example:
const Person = struct {
name: []const u8,
age: ?i32,
tags: []const []const u8,
};
const schema = try json.generateSchema(Person, allocator, .{});
defer json.freeSchema(allocator, schema);
const output = try json.stringify(allocator, schema);
defer allocator.free(output);
// {"type":"object","properties":{"name":{"type":"string"},...}}
Streaming Tokenizer #
Tokenizer #
Low-level token-based parsing. The tokenizer decodes strings into one buffer
that it reuses across next(), peek() and readValue(); deinit() frees
it, so pair every init with a deinit:
var tokenizer = json.Tokenizer.init(allocator, input, .{});
defer tokenizer.deinit();
while (true) {
const token = try tokenizer.next();
if (token.type == .eof) break;
switch (token.type) {
.object_start => std.debug.print("object start\n", .{}),
.string => std.debug.print("String: {s}\n", .{token.bytes}),
// ...
else => {},
}
}
ReaderTokenizer #
Tokenize from any reader:
var file = try std.fs.cwd().openFile("data.json", .{});
defer file.close();
var tokenizer = json.readerTokenizer(allocator, file.reader(), .{});
defer tokenizer.deinit();
while (try tokenizer.next()) |token| {
// Process token
}
readValue #
Read complete values from tokenizer:
var tokenizer = json.Tokenizer.init(allocator, input, .{});
defer tokenizer.deinit();
const value = try json.readValue(&tokenizer);
defer json.freeValue(allocator, value);
SIMD Primitives #
The library ships a set of standalone, tested SIMD scanning primitives in the
simd module (re-exported as json.simd_mod). They are opt-in and are
not wired into the default parser/tokenizer/unmarshaler, which are scalar.
They operate on raw byte slices and do not, by themselves, perform JSON
validation, escape decoding, or surrogate-pair handling.
SimdParser #
A standalone helper that wraps the scanning primitives with cursor and
line/column tracking. It is independent of the default json.parse entry
point and does not build a Value/Document tree:
var sp = json.SimdParser.init(input, .{});
sp.skipWhitespace();
const next = sp.findNextStructural();
_ = next;
Batch Helpers #
var processor = json.BatchProcessor.init(allocator);
// Locate string spans: returns []{ start, end }
const spans = try processor.findAllStrings(input);
defer allocator.free(spans);
// Count structural characters
const counts = processor.countStructurals(input);
// counts.objects, counts.arrays, counts.strings, counts.colons, counts.commas
Free-Function Primitives #
// Skip leading whitespace (returns index of first non-whitespace byte)
const first = json.skipWhitespaceSIMD(input);
// Validate UTF-8 (SIMD ASCII fast path with scalar fallback)
const valid = json.validateUtf8SIMD(input);
// Runtime check for SIMD availability
const have_simd = json.simdAvailable();
Feature Detection #
const features = json.simd_mod.detectFeatures();
std.debug.print("SIMD: {s}, Width: {d}\n", .{
features.describe(),
features.recommended_width,
});
SIMDFeatures #
pub const SIMDFeatures = struct {
has_simd: bool,
has_sse42: bool,
has_avx2: bool,
has_avx512f: bool,
has_neon: bool,
recommended_width: u8,
pub fn describe(self: SIMDFeatures) []const u8;
};
Async Parsing #
AsyncParser #
Incremental async JSON parser:
var parser = json.AsyncParser.init(allocator, .{});
defer parser.deinit();
// Feed data incrementally
try parser.feed(chunk1);
try parser.feed(chunk2);
// Get result when complete
if (try parser.finish()) |doc| {
defer doc.deinit();
// Use parsed document
}
Error Recovery #
var parser = json.AsyncParser.init(allocator, .{
.recovery_mode = .skip_value,
});
// Parser will attempt to recover from errors
try parser.feed(malformed_json);
std.debug.print("Recovered from {d} errors\n", .{parser.errors_recovered});
AsyncIterator #
Iterate over streaming JSON values:
var iter = json.AsyncIterator.init(allocator);
defer iter.deinit();
while (try iter.feed(chunk)) |value| {
defer iter.freeValue(value);
// Process complete value
}
JSON-LD #
ldExpand #
Expand JSON-LD document:
pub fn ldExpand(
allocator: std.mem.Allocator,
document: Value,
options: LdOptions
) !Value
ldCompact #
Compact with context:
pub fn ldCompact(
allocator: std.mem.Allocator,
document: Value,
context: Value,
options: LdOptions
) !Value
ldFlatten / ldFrame #
pub fn ldFlatten(
allocator: std.mem.Allocator,
document: Value,
context: ?Value,
options: LdOptions
) !Value
pub fn ldFrame(
allocator: std.mem.Allocator,
document: Value,
frame: Value,
options: LdOptions
) !Value
ldToNQuads #
Convert to N-Quads format:
pub fn ldToNQuads(
allocator: std.mem.Allocator,
document: Value,
options: LdOptions
) ![]const u8
Remote Context Loading #
var loader = json.jsonld_mod.RemoteContextLoader.init(allocator);
defer loader.deinit();
// Preload contexts
try loader.preloadContext("https://schema.org/", schema_org_context);
// Load with caching
const ctx = try loader.loadContext("https://schema.org/");
CBOR Encoding (RFC 8949) #
cborEncode #
pub fn cborEncode(
allocator: std.mem.Allocator,
value: Value,
options: CborEncodeOptions
) ![]u8
cborDecode #
pub fn cborDecode(
allocator: std.mem.Allocator,
data: []const u8,
options: CborDecodeOptions
) !Value
Streaming Decoder #
var decoder = json.cbor_mod.StreamingDecoder.init(allocator);
defer decoder.deinit();
while (have_data) {
try decoder.feed(chunk);
while (true) {
const result = decoder.decodeNext();
switch (result) {
.value => |v| {
// Complete value decoded
},
.need_data => break,
.done => break,
.err => |e| return e,
}
}
}
cborToDiagnostic #
Convert CBOR to diagnostic notation:
pub fn cborToDiagnostic(
allocator: std.mem.Allocator,
data: []const u8
) ![]const u8
Example:
const json_val = doc.root;
const cbor_bytes = try json.cborEncode(allocator, json_val, .{});
defer allocator.free(cbor_bytes);
const decoded = try json.cborDecode(allocator, cbor_bytes, .{});
defer json.cborFree(allocator, decoded);
Options Reference #
ParseOptions #
pub const ParseOptions = struct {
max_bytes: usize = 8 * 1024 * 1024,
max_depth: u16 = 256,
max_nodes: u32 = 1_000_000,
allow_bom: bool = true,
unique_keys: bool = true,
json5: bool = false, // Enable JSON5 extensions
};
EncodeOptions #
pub const EncodeOptions = struct {
indent: ?[]const u8 = null,
emit_null_optional_fields: bool = false,
escape_non_ascii: bool = false,
escape_slash: bool = false,
allocator: ?std.mem.Allocator = null,
deterministic: bool = false,
stringify_numbers: bool = false,
default_binary_format: BinaryFormat = .utf8,
allow_nan_infinity: bool = false,
format_nil_slice_as_null: bool = false,
use_fmt_formatter: bool = false,
};
Presets: json.encode_mod.minified, pretty_2, pretty_4, pretty_tab
UnmarshalOptions #
pub const UnmarshalOptions = struct {
ignore_unknown_fields: bool = true,
use_field_tags: bool = true,
default_binary_format: BinaryFormat = .utf8,
allow_nan_infinity: bool = false,
match_case_insensitive: bool = false,
};
SchemaOptions #
pub const SchemaOptions = struct {
draft: Draft = .draft2020_12,
max_schema_bytes: usize = 8 * 1024 * 1024,
max_ref_depth: u16 = 64,
max_errors: u16 = 8,
format_assertion: bool = true,
regex_max_steps: u32 = 1_000_000,
custom_formats: ?[]const FormatValidator = null,
};
AsyncParseOptions #
pub const AsyncParseOptions = struct {
max_depth: u16 = 256,
max_nodes: u32 = 1_000_000,
chunk_size: usize = 4096,
recovery_mode: ErrorRecoveryMode = .none,
};
pub const ErrorRecoveryMode = enum {
none,
skip_value,
resync,
};
CborEncodeOptions / CborDecodeOptions #
pub const CborEncodeOptions = struct {
use_definite_length: bool = true,
tag_datetime: bool = false,
canonical: bool = false,
};
pub const CborDecodeOptions = struct {
max_depth: u16 = 256,
max_bytes: usize = 8 * 1024 * 1024,
allow_unknown_tags: bool = true,
};
Error Types #
UnmarshalError #
pub const UnmarshalError = error{
TypeMismatch,
MissingField,
UnknownField,
InvalidFormat,
IntegerOverflow,
DuplicateField,
OutOfMemory,
JsonParse,
};
UnmarshalErrorInfo #
pub const UnmarshalErrorInfo = struct {
path: []const u8, // JSON Pointer path
kind: UnmarshalErrorKind,
expected: ?[]const u8 = null,
found: ?[]const u8 = null,
message: []const u8,
pub fn format(self: UnmarshalErrorInfo, allocator: std.mem.Allocator) ![]u8;
};
PathError #
pub const PathError = error{
InvalidPath,
SyntaxError,
OutOfMemory,
};
PatchError #
pub const PatchError = error{
InvalidPatch,
InvalidPath,
PathNotFound,
TestFailed,
OutOfMemory,
};
CborError #
pub const CborError = error{
InvalidCbor,
UnsupportedType,
OutOfMemory,
MaxDepthExceeded,
MaxBytesExceeded,
};
Submodules #
Advanced access to internal modules:
| Module | Description |
|---|---|
json.parser |
Low-level parser |
json.emitter |
Low-level emitter |
json.encode_mod |
Encoding implementation |
json.unmarshal_mod |
Unmarshaling implementation |
json.tokenizer_mod |
Streaming tokenizer |
json.time_mod |
Time types |
json.unicode |
UTF-8 utilities |
json.number_mod |
Number parsing |
json.pointer_mod |
JSON Pointer (RFC 6901) |
json.schema_mod |
JSON Schema internals |
json.regex |
ECMA-262 regex (Pike NFA; supports alternation | and non-capturing grouping (); capture-group extraction not implemented) |
json.patch_mod |
JSON Patch |
json.jsonl_mod |
JSON Lines |
json.jsonpath_mod |
JSONPath (RFC 9535 function extensions not implemented; unknown filter predicates evaluate to no-match) |
json.schema_gen_mod |
Schema generation |
json.simd_mod |
Opt-in SIMD scanning primitives (not used by default parser) |
json.async_mod |
Async parsing |
json.jsonld_mod |
JSON-LD |
json.cbor_mod |
CBOR |