SkillAgentSearch skills...

Serde.zig

Universal serialization for Zig: JSON, Yaml, XML, MessagePack, TOML, CSV and more from a single API. msgpack.org[Zig]

Install / Use

/learn @OrlovEvgeny/Serde.zig
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

serde.zig

Build Release Zig

Serialization framework for Zig

Uses Zig's comptime reflection (@typeInfo) to serialize and deserialize any Zig type across JSON, MessagePack, TOML, YAML, XML, ZON, and CSV without macros, code generation, or runtime type information.

Table of Contents

Why serde.zig?

No boilerplate. No macros, no code generation, no build steps. Just declare a struct and serialize it. Zig's comptime reflection handles everything at compile time.

Seven formats, one API. JSON, MessagePack, TOML, YAML, XML, ZON, and CSV all share the same toSlice/fromSlice/toWriter/fromReader interface. Learn once, use everywhere.

Out-of-band schemas. Serialize the same type differently in different contexts without modifying the type itself. Essential for third-party types and API versioning.

Zero-copy JSON. fromSliceBorrowed returns string slices that point directly into the input buffer when no escape sequences are present. No allocation, no copying.

Comptime validation. Invalid types, missing fields, and incorrect option names are caught at compile time, not at runtime.

Quick Start

const serde = @import("serde");

const User = struct {
    name: []const u8,
    age: u32,
    email: ?[]const u8 = null,
};

// Serialize to JSON
const json_bytes = try serde.json.toSlice(allocator, User{
    .name = "Alice",
    .age = 30,
    .email = "alice@example.com",
});
// => {"name":"Alice","age":30,"email":"alice@example.com"}

// Deserialize from JSON
const user = try serde.json.fromSlice(User, allocator, json_bytes);

Installation

Latest version from master:

zig fetch --save git+https://github.com/OrlovEvgeny/serde.zig

Specific release:

zig fetch --save https://github.com/OrlovEvgeny/serde.zig/archive/refs/tags/v1.0.2.tar.gz

Then in your build.zig:

const serde_dep = b.dependency("serde", .{
    .target = target,
    .optimize = optimize,
});
exe.root_module.addImport("serde", serde_dep.module("serde"));

Requires Zig 0.15.0 or later.

Formats

| Format | Module | Serialize | Deserialize | |--------|--------|-----------|-------------| | JSON | serde.json | + | + | | MessagePack | serde.msgpack | + | + | | TOML | serde.toml | + | + | | YAML | serde.yaml | + | + | | XML | serde.xml | + | + | | ZON | serde.zon | + | + | | CSV | serde.csv | + | + |

Every format exposes the same API:

// Serialization
const bytes = try serde.json.toSlice(allocator, value);
try serde.json.toWriter(&writer, value);

// Deserialization
const val = try serde.json.fromSlice(T, allocator, bytes);
const val = try serde.json.fromReader(T, allocator, &reader);

Supported Types

  • bool, i8..i128, u8..u128, f16..f128
  • []const u8, []u8, [:0]const u8 (strings)
  • ?T (optionals, serialized as value or null)
  • [N]T (fixed-length arrays)
  • []T, []const T (slices)
  • Structs with named fields, nested arbitrarily
  • Tuples (struct { i32, bool }, serialized as arrays)
  • Enums (as string name or integer)
  • Tagged unions (union(enum), four tagging styles)
  • *T, *const T (pointers, followed transparently)
  • std.StringHashMap(V) (maps)
  • void (serialized as null)

Examples

Nested structs

const Address = struct {
    street: []const u8,
    city: []const u8,
    zip: []const u8,
};

const Person = struct {
    name: []const u8,
    age: u32,
    address: Address,
    tags: []const []const u8,
};

const person = Person{
    .name = "Bob",
    .age = 25,
    .address = .{ .street = "123 Main St", .city = "Springfield", .zip = "62704" },
    .tags = &.{ "admin", "active" },
};

const json = try serde.json.toSlice(allocator, person);
const msgpack = try serde.msgpack.toSlice(allocator, person);
const yaml = try serde.yaml.toSlice(allocator, person);
const xml = try serde.xml.toSlice(allocator, person);

Arena allocator (recommended for deserialization)

Deserialization allocates memory for strings, slices, and nested structures. Use an ArenaAllocator for easy cleanup:

var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();

const user = try serde.json.fromSlice(User, arena.allocator(), json_bytes);

Zero-copy deserialization

When strings in the JSON input contain no escape sequences, fromSliceBorrowed returns slices pointing directly into the input buffer:

const input = "{\"name\":\"alice\",\"id\":1}";
const msg = try serde.json.fromSliceBorrowed(Msg, allocator, input);
// msg.name points into input, input must outlive msg

Pretty-printed output

const pretty = try serde.json.toSliceWith(allocator, value, .{ .pretty = true, .indent = 2 });
// {
//   "name": "Alice",
//   "age": 30
// }

Tagged unions

const Command = union(enum) {
    ping: void,
    execute: struct { query: []const u8 },
    quit: void,
};

const cmd = Command{ .execute = .{ .query = "SELECT 1" } };
const bytes = try serde.json.toSlice(allocator, cmd);
// => {"execute":{"query":"SELECT 1"}}

Enums

const Color = enum { red, green, blue };

const bytes = try serde.json.toSlice(allocator, Color.blue);
// => "blue"

const color = try serde.json.fromSlice(Color, allocator, bytes);
// => Color.blue

Maps

var map = std.StringHashMap(i32).init(allocator);
defer map.deinit();
try map.put("a", 1);
try map.put("b", 2);

const bytes = try serde.json.toSlice(allocator, map);
// => {"a":1,"b":2}

CSV

const Record = struct {
    name: []const u8,
    age: u32,
    active: bool,
};

const records: []const Record = &.{
    .{ .name = "Alice", .age = 30, .active = true },
    .{ .name = "Bob", .age = 25, .active = false },
};

const csv_bytes = try serde.csv.toSlice(allocator, records);
// name,age,active
// Alice,30,true
// Bob,25,false

TOML

const Config = struct {
    title: []const u8,
    port: u16 = 8080,
    database: struct {
        host: []const u8,
        name: []const u8,
    },
};

const cfg = try serde.toml.fromSlice(Config, arena.allocator(),
    \\title = "myapp"
    \\port = 3000
    \\
    \\[database]
    \\host = "localhost"
    \\name = "mydb"
);

YAML

const Server = struct {
    host: []const u8,
    port: u16,
    debug: bool,
};

const yaml_input =
    \\host: localhost
    \\port: 8080
    \\debug: true
;

const server = try serde.yaml.fromSlice(Server, arena.allocator(), yaml_input);

const yaml_bytes = try serde.yaml.toSlice(allocator, server);
// host: localhost
// port: 8080
// debug: true

XML

const User = struct {
    id: u64,
    name: []const u8,
    role: []const u8,

    pub const serde = .{
        .xml_attribute = .{.id},
        .xml_root = "user",
    };
};

const xml_bytes = try serde.xml.toSlice(allocator, User{
    .id = 42,
    .name = "Alice",
    .role = "admin",
});
// <?xml version="1.0" encoding="UTF-8"?>
// <user id="42"><name>Alice</name><role>admin</role></user>

const user = try serde.xml.fromSlice(User, arena.allocator(), xml_bytes);

Fields listed in xml_attribute are serialized as XML attributes on the root element. All other fields become child elements.

ZON

Produces valid .zon files:

const bytes = try serde.zon.toSlice(allocator, Config{
    .title = "myapp",
    .port = 3000,
    .database = .{ .host = "localhost", .name = "mydb" },
});
// .{
//     .title = "myapp",
//     .port = 3000,
//     .database = .{
//         .host = "localhost",
//         .name = "mydb",
//     },
// }

Serde Options

Customize serialization behavior by declaring pub const serde on your types. All options are resolved at comptime.

Field renaming

const User = struct {
    user_id: u64,
    first_name: []const u8,
    last_name: []const u8,

    pub const serde = .{
        .rename = .{ .user_id = "id" },
        .rename_all = serde.NamingConvention.camel_case,
    };
};

// Serializes as: {"id":1,"firstName":"Alice","lastName":"Smith"}

Available conventions: .camel_case, .snake_case, .pascal_case, .kebab_case, .SCREAMING_SNAKE_CASE.

Asymmetric renaming

Use different names for serialization and deserialization. This is essential for API evolution,

Related Skills

View on GitHub
GitHub Stars33
CategoryDevelopment
Updated5h ago
Forks0

Languages

Zig

Security Score

95/100

Audited on Apr 9, 2026

No findings