Data serialization based on std.json and std.zon (Zig 0.16.0).
Capability Boundaries
✅ Strengths
- Parse JSON into Zig structs
- Serialize Zig structs to JSON
- Dynamic JSON value handling (untyped scenarios)
- Custom serialization (
jsonStringify、jsonParseFromValue) - Streaming JSON processing (large file scenarios)
- ZON (Zig Object Notation) parsing
⚠️ Prerequisites
- Confirm the Zig version (
zig version)
❌ Out of Scope
- Network transmission of JSON → use the
zig-httpskill - ZON for build.zig.zon → use the
zig-build-systemskill
When to Use
- "Parse this JSON string"
- "Serialize a struct to JSON"
- "Handle dynamic JSON data"
Data Privacy
This skill does not collect, store, or transmit any user data.
Workflow
Step 1. Determine the input — statically typed (struct) or dynamic (Value)
Step 2. Parse — parseFromSlice or parseFromSliceLeaky
Step 3. Use the data — access fields/iterate
Step 4. Clean up — parsed.deinit() free memory
Parse JSON
Parse into a struct (recommended)
const Config = struct {
name: []const u8,
port: u16,
enabled: bool = true,
};
const json_str = \\{"name": "server", "port": 8080};
const parsed = try std.json.parseFromSlice(Config, allocator, json_str, .{});
defer parsed.deinit();
const config = parsed.value;
// config.name == "server", config.port == 8080, config.enabled == true
Parse options
const parsed = try std.json.parseFromSlice(T, allocator, json_str, .{
.duplicate_field_behavior = .@"error", // .use_first | .use_last
.ignore_unknown_fields = true,
.max_value_len = 4096,
.parse_numbers = true,
});
Supported type mappings
| Zig type | JSON value |
|---|---|
bool |
true, false |
i32, u64 etc. |
number (or string, optional) |
f32, f64 |
number |
[]const u8 |
string |
[]T |
array |
struct |
object |
?T |
value or null |
union(enum) |
tagged object |
std.json.Value |
any JSON |
Pointer type handling
const Config = struct {
name: []const u8, // 引用了 parsed 的内存
tags: []const []const u8, // 同上
};
// ⚠️ parsed.deinit() 后这些字段不可用
// 如需长期持有:allocator.dupe()
// 使用 arena 简化生命周期
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
const parsed = try std.json.parseFromSliceLeaky(Config, arena.allocator(), json_str, .{});
// arena.deinit() 一次性释放所有内存
Serialization
Struct → JSON string
const Config = struct {
name: []const u8,
port: u16,
};
const config = Config{ .name = "server", .port = 8080 };
var buf: [256]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try std.json.stringify(config, .{}, &writer);
const json = writer.buffered();
// {"name":"server","port":8080}
Pretty-print output
try std.json.stringify(config, .{ .whitespace = .indent_2 }, &writer);
// {
// "name": "server",
// "port": 8080
// }
Omit default values
try std.json.stringify(value, .{ .emit_null_optional_fields = false }, &writer);
Dynamic JSON
Untyped parsing
const raw = \\{"users":[{"name":"Alice"}],"count":1};
const root = try std.json.parseFromSlice(std.json.Value, allocator, raw, .{});
defer root.deinit();
const obj = root.value.object;
const count = obj.get("count").?.integer;
const users = obj.get("users").?.array;
for (users) |user| {
const name = user.object.get("name").?.string;
}
Build dynamic JSON
var obj = std.json.ObjectMap.init(allocator);
try obj.put("name", std.json.Value{ .string = "Alice" });
try obj.put("age", std.json.Value{ .integer = 30 });
// 序列化
var buf: [256]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try std.json.stringify(std.json.Value{ .object = obj }, .{}, &writer);
Custom serialization
Customize for structs
const Point = struct {
x: i32,
y: i32,
pub fn jsonStringify(self: Point, jws: *std.json.WriteStream(.{})) !void {
try jws.beginObject();
try jws.objectField("coordinates");
try jws.beginArray();
try jws.write(self.x);
try jws.write(self.y);
try jws.endArray();
try jws.endObject();
}
};
ZON(Zig Object Notation)
const std = @import("std");
const Config = struct {
name: []const u8,
version: []const u8,
};
const zon_str =
\\.{
\\ .name = "myapp",
\\ .version = "0.1.0",
\\}
;
const parsed = try std.zon.parseFromSlice(Config, allocator, zon_str, .{});
defer parsed.deinit();
// parsed.value.name == "myapp"
Streaming (large files)
// 逐 token 扫描(避免将整个 JSON 加载到内存)
const file = try std.fs.cwd().openFile("large.json", .{});
defer file.close();
var buf_reader = std.io.bufferedReader(file.reader());
const reader = buf_reader.reader();
var scan_state = std.json.Scanner.init(allocator, reader.any());
defer scan_state.deinit();
while (true) {
const token = scan_state.next() orelse break;
switch (token) {
.object_begin => {},
.string => |s| { /* handle string */ },
.number => |n| { /* handle number */ },
else => {},
}
}
Gotchas
parsed.deinit()free referenced memory — after parsing,[]const u8fields reference memory internal to parsed and are unavailable after deinit- Use an Arena to simplify lifetimes —
parseFromSliceLeakytogether withArenaAllocatorto avoid copying field by field - Use streaming for large JSON —
parseFromSliceloads the entire JSON; for very large files useScannerstreaming integernote the sign of the field —std.json.Value.integerisi64; large unsigned numbers require extra handling- ZON and JSON syntax differ — ZON uses
.field = valueinstead of"field": value, and key names cannot be quoted
FAQ
Q:parseFromSlice and parseFromSliceLeaky ?
A: The former returns Parsed(T) requires manual deinit(), the latter uses an arena for one-time deallocation, suitable for short-term use.
Q: How do I handle very large JSON?
A: Use std.json.Scanner to process token by token, or use a streaming Reader to parse while reading.
Q: When should ZON vs JSON be used? A: Zig project configuration uses ZON (build.zig.zon), while external API communication uses JSON.