zerde is a comptime-specialized serialization framework for Zig.
Point it at a Zig type and a format, and it generates a typed serializer or deserializer for that exact combination.
What you get:
- one typed API across JSON, TOML, YAML, CBOR, BSON, MessagePack, and binary
- fast read and write paths without a required runtime value tree
- per-type and per-call customization for field renames and wire-shape policy
- owned, arena-backed, and aliased slice parse entrypoints
- wasm/WASI pointer+length helpers for moving typed values across JS boundaries and parsing JSON, YAML, MessagePack, and other format payloads inside the module
const std = @import("std");
const zerde = @import("zerde");
const StrawHat = struct {
name: []const u8,
bounty: u32,
role: enum { captain, navigator, cook, shipwright },
pub const serde = .{
.rename_all = .snake_case,
};
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
const crew_mate = StrawHat{
.name = "Franky",
.bounty = 394_000_000,
.role = .shipwright,
};
var out: std.Io.Writer.Allocating = .init(allocator);
defer out.deinit();
try zerde.serialize(zerde.json, &out.writer, crew_mate);
var decoded = try zerde.parseSliceOwned(zerde.json, StrawHat, allocator, out.written());
defer decoded.deinit();
std.debug.print("{s}\n", .{out.written()});
}| Format | Serialize | Reader deserialize | Slice deserialize | Aliased slice parse | Notes |
|---|---|---|---|---|---|
| Binary | yes | yes | yes | yes | Schema-driven compact binary format |
| BSON | yes | yes | yes | yes | Typed BSON path |
| MessagePack | yes | yes | yes | yes | Typed MessagePack path |
| JSON | yes | yes | yes | yes | Fully typed fast path, benchmarked against std.json |
| TOML | yes | yes | yes | yes | Practical TOML subset centered on scalars, arrays, tables, and arrays-of-tables |
| CBOR | yes | yes | yes | yes | Definite-length writer; read accepts definite and indefinite arrays/maps |
| YAML | yes | yes | yes | yes | Practical block-YAML subset with block mappings, block sequences, and flow scalar arrays |
zerde is not limited to its compact binary transport in wasm.
The zerde.wasm helpers can:
- serialize typed Zig values into wasm-friendly pointer+length buffers
- parse JSON, YAML, MessagePack, and other supported payloads inside the module
- reserialize those typed values back into JSON, binary, or another format before handing bytes back to JS
const std = @import("std");
const zerde = @import("zerde");
const CrewManifest = struct {
captainName: []const u8,
bounty: u32,
shipwright: bool,
pub const serde = .{
.rename_all = .snake_case,
};
};
pub fn normalizeJson(allocator: std.mem.Allocator, input: []const u8) !zerde.wasm.OwnedBuffer {
const manifest = try zerde.wasm.parseFormatWith(zerde.json, CrewManifest, allocator, zerde.wasm.sliceDescriptor(input), .{
.rename_all = .snake_case,
}, .{});
defer zerde.free(allocator, manifest);
return zerde.wasm.serializeFormatOwnedWith(zerde.json, allocator, manifest, .{
.rename_all = .snake_case,
}, .{});
}Browser-oriented wasm examples live in examples/, and you can build them with zig build examples.
Add zerde as a Zig dependency:
zig fetch --save git+https://github.com/leostera/zerdeThen import it from your build.zig:
const zerde_dep = b.dependency("zerde", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zerde", zerde_dep.module("zerde"));And from your Zig code:
const zerde = @import("zerde");Benchmark history and per-format runs live in bench/.
zerde accepts configuration at two levels:
- call-site config passed to
serializeWith,deserializeWith,parseSliceWith, orparseSliceAliasedWith - per-type config through
pub const serde = .{ ... }
Supported typed-layer options today:
rename_allomit_null_fieldsdeny_unknown_fields- per-field
rename
Example:
const std = @import("std");
const zerde = @import("zerde");
const User = struct {
firstName: []const u8,
createdAt: i64,
nickname: ?[]const u8,
pub const serde = .{
.rename_all = .snake_case,
.fields = .{
.createdAt = .{ .rename = "created_at_unix" },
},
};
};
pub fn main() !void {
var out: std.Io.Writer.Allocating = .init(std.heap.page_allocator);
defer out.deinit();
try zerde.serializeWith(zerde.json, &out.writer, User{
.firstName = "Franky",
.createdAt = 42,
.nickname = null,
}, .{
.omit_null_fields = true,
}, .{});
}