JSON
const std = @import("std");
const expect = std.testing.expect;
const Config = struct {
name: []const u8,
port: u16,
debug: bool = false, // defaults cover missing fields
};
test "parse into a struct" {
const gpa = std.testing.allocator;
const text =
\\{ "name": "server", "port": 8080 }
;
// The parse result owns its allocations; deinit frees them all at once.
const parsed = try std.json.parseFromSlice(Config, gpa, text, .{});
defer parsed.deinit();
try expect(std.mem.eql(u8, parsed.value.name, "server"));
try expect(parsed.value.port == 8080);
try expect(parsed.value.debug == false);
}
test "unknown fields are rejected by default" {
const gpa = std.testing.allocator;
const text =
\\{ "name": "x", "port": 1, "extra": true }
;
try std.testing.expectError(
error.UnknownField,
std.json.parseFromSlice(Config, gpa, text, .{}),
);
// Opt out when the payload may carry fields you do not model.
const lenient = try std.json.parseFromSlice(Config, gpa, text, .{
.ignore_unknown_fields = true,
});
defer lenient.deinit();
try expect(lenient.value.port == 1);
}
test "dynamic values when the shape is unknown" {
const gpa = std.testing.allocator;
const parsed = try std.json.parseFromSlice(
std.json.Value,
gpa,
\\{ "a": [1, 2, 3] }
,
.{},
);
defer parsed.deinit();
const array = parsed.value.object.get("a").?.array;
try expect(array.items.len == 3);
try expect(array.items[0].integer == 1);
}
test "serialise" {
const gpa = std.testing.allocator;
var out: std.Io.Writer.Allocating = .init(gpa);
defer out.deinit();
try std.json.Stringify.value(
Config{ .name = "out", .port = 99, .debug = true },
.{},
&out.writer,
);
try expect(std.mem.indexOf(u8, out.written(), "\"port\":99") != null);
}Parsing into a struct
parseFromSlice(T, gpa, text, .{}) maps JSON onto your type using its field
names. The result owns every allocation it made:
const parsed = try std.json.parseFromSlice(Config, gpa, text, .{});
defer parsed.deinit();
parsed.value.port;
Note parsed.value: the payload lives behind the owner so a single deinit
frees the whole tree. Strings in parsed.value point into that arena, so they
die with it; duplicate anything you need to outlive the parse.
Missing and unknown fields
A field with a default is optional in the input. An unknown field is an error by default:
error.UnknownField
That default is the right one: it catches typos in config files. Opt out deliberately when consuming a payload you do not fully model:
.{ .ignore_unknown_fields = true }
When the shape is not known
Parse into std.json.Value for a dynamic tree, then walk .object, .array,
.integer, .string, and friends. Prefer a concrete struct where you can:
you get validation and field names checked at compile time.
Writing
std.json.Stringify.value(x, .{}, &writer) serialises to any writer. Pair it
with std.Io.Writer.Allocating to produce a string.