json
building and parsing json. zig 0.15 has std.json.Stringify for output and std.json.parse for input.
building json
use json.Stringify with a writer. call methods to build the structure incrementally:
const std = @import("std");
const json = std.json;
fn buildJson(allocator: std.mem.Allocator, data: MyData) ![]u8 {
var output: std.Io.Writer.Allocating = .init(allocator);
errdefer output.deinit();
var jw: json.Stringify = .{ .writer = &output.writer };
try jw.beginObject();
try jw.objectField("name");
try jw.write(data.name); // strings, ints, floats, bools
try jw.objectField("count");
try jw.write(data.count);
try jw.objectField("items");
try jw.beginArray();
for (data.items) |item| {
try jw.write(item);
}
try jw.endArray();
try jw.endObject();
return output.toOwnedSlice();
}
key methods:
beginObject()/endObject()-{and}beginArray()/endArray()-[and]objectField("key")- write a key, callwrite()next for the valuewrite(value)- writes any json-serializable value (handles UTF-8 and escaping correctly)
fixed buffer (no allocation)
when you have a stack buffer and don't want to allocate:
fn toJson(data: MyData, buf: []u8) []const u8 {
var w: std.Io.Writer = .fixed(buf);
var jw: std.json.Stringify = .{ .writer = &w };
jw.beginObject() catch return "{}";
jw.objectField("name") catch return "{}";
jw.write(data.name) catch return "{}";
jw.endObject() catch return "{}";
return w.buffered(); // slice of what was written
}
key difference: use std.Io.Writer = .fixed(buf) and call w.buffered() to get the written slice.
see: coral/backend/src/entities.zig
raw json passthrough
when you have a json string that's already valid and want to embed it without re-parsing:
try jw.objectField("nested");
try jw.beginWriteRaw();
try jw.writer.writeAll(raw_json_string);
jw.endWriteRaw();
useful when proxying json from external apis.
gotcha: numbers vs strings
otlp and other protocols care about the difference. timestamps are often strings (to avoid precision loss with large nanosecond values), but counts are numbers:
// timestamp as string
try jw.objectField("timeUnixNano");
var buf: [32]u8 = undefined;
const s = std.fmt.bufPrint(&buf, "{d}", .{timestamp_ns}) catch unreachable;
try jw.write(s); // "1234567890000000000"
// count as number
try jw.objectField("count");
try jw.write(count); // 42