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:
zerde.Value tree for transcoding and schema-less toolingCurrent benchmark snapshot on the repo's mixed nested workload:
2.1x faster than std.json on reads and 1.2x faster on writes1.3x faster than std.zon on reads and 1.1x faster on writes1.7x faster than zig-toml on reads and 1.1x faster on writes8x faster than zbor on reads and 1.8x faster on writes2x faster than zig-yaml on reads and 2.5x faster on writes9x faster than zig-bson on reads and 3x to 4x faster on writes4x to 5x faster than bufzilla on reads and 3x to 4x faster on writesconst 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 |
| ZON | yes | yes | yes | no | Typed ZON path, benchmarked against std.zon; slice parse currently owns decoded strings |
| 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:
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,
}, .{});
}
Native format examples and 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/zerde#0.1.0
Then 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 also ships with a small transcoder for self-describing formats.
Build it with:
zig build transcode
Then use it like:
./zig-out/bin/zerde-transcode --from json --to yaml crew.json
It currently supports json, zon, toml, yaml, cbor, bson, and msgpack.
zerde accepts configuration at two levels:
serializeWith, deserializeWith, parseSliceWith, or parseSliceAliasedWithpub const serde = .{ ... }Supported typed-layer options today:
rename_allomit_null_fieldsdeny_unknown_fieldsrenameExample:
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,
}, .{});
}
93 commits
Zig
100.0%
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:
zerde.Value tree for transcoding and schema-less toolingCurrent benchmark snapshot on the repo's mixed nested workload:
2.1x faster than std.json on reads and 1.2x faster on writes1.3x faster than std.zon on reads and 1.1x faster on writes1.7x faster than zig-toml on reads and 1.1x faster on writes8x faster than zbor on reads and 1.8x faster on writes2x faster than zig-yaml on reads and 2.5x faster on writes9x faster than zig-bson on reads and 3x to 4x faster on writes4x to 5x faster than bufzilla on reads and 3x to 4x faster on writesconst 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 |
| ZON | yes | yes | yes | no | Typed ZON path, benchmarked against std.zon; slice parse currently owns decoded strings |
| 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:
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,
}, .{});
}
Native format examples and 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/zerde#0.1.0
Then 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 also ships with a small transcoder for self-describing formats.
Build it with:
zig build transcode
Then use it like:
./zig-out/bin/zerde-transcode --from json --to yaml crew.json
It currently supports json, zon, toml, yaml, cbor, bson, and msgpack.
zerde accepts configuration at two levels:
serializeWith, deserializeWith, parseSliceWith, or parseSliceAliasedWithpub const serde = .{ ... }Supported typed-layer options today:
rename_allomit_null_fieldsdeny_unknown_fieldsrenameExample:
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,
}, .{});
}
93 commits
Zig
100.0%