SuperAPI AI Directory

zig-json

Coding & development

View repository

Updated 10-15 21:48

About this skill

Zig JSON Processing

Data serialization based on std.json and std.zon (Zig 0.16.0).

Capability Boundaries

✅ Strengths

  1. Parse JSON into Zig structs
  2. Serialize Zig structs to JSON
  3. Dynamic JSON value handling (untyped scenarios)
  4. Custom serialization (jsonStringify、jsonParseFromValue)
  5. Streaming JSON processing (large file scenarios)
  6. ZON (Zig Object Notation) parsing

⚠️ Prerequisites

  1. Confirm the Zig version (zig version)

❌ Out of Scope

  1. Network transmission of JSON → use the zig-http skill
  2. ZON for build.zig.zon → use the zig-build-system skill

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

  1. parsed.deinit() free referenced memory — after parsing, []const u8 fields reference memory internal to parsed and are unavailable after deinit
  2. Use an Arena to simplify lifetimes — parseFromSliceLeaky together with ArenaAllocator to avoid copying field by field
  3. Use streaming for large JSON — parseFromSlice loads the entire JSON; for very large files use Scanner streaming
  4. integer note the sign of the field — std.json.Value.integer is i64; large unsigned numbers require extra handling
  5. ZON and JSON syntax differ — ZON uses .field = value instead 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.