std.json Stringify em Zig — Referência e Exemplos

std.json Stringify — Serialização para JSON

A serialização JSON do Zig converte valores Zig (structs, arrays, slices, optionals, enums) em strings JSON válidas. O sistema usa os metadados de tipo do Zig em tempo de compilação para gerar código de serialização otimizado, sem reflexão em runtime. É possível controlar a formatação (compacta ou indentada), o tratamento de campos nulos e a emissão de campos opcionais.

Visão Geral

const std = @import("std");
const json = std.json;

Funções Principais

// Serializa para um Writer
pub fn stringify(
    value: anytype,
    options: StringifyOptions,
    writer: anytype,
) !void

// Serializa para uma string alocada
pub fn stringifyAlloc(
    allocator: Allocator,
    value: anytype,
    options: StringifyOptions,
) ![]u8

StringifyOptions

pub const StringifyOptions = struct {
    // Controle de whitespace
    whitespace: Whitespace = .minified,

    // Emite campos opcionais como "null" ou omite
    emit_null_optional_fields: bool = true,

    pub const Whitespace = union(enum) {
        minified,
        indent_1,
        indent_2,
        indent_3,
        indent_4,
        indent_8,
        indent_tab,
    };
};

Mapeamento de Tipos

Tipo ZigJSON
booltrue / false
inteirosnumber
floatsnumber
[]const u8string
?T (null)null
?T (valor)valor serializado
structobject
array / slicearray
enumstring (nome do tag)

Exemplo 1: Serialização de Struct Aninhada

const std = @import("std");

const Pedido = struct {
    id: u64,
    cliente: Cliente,
    itens: []const Item,
    total: f64,
    status: Status,
    observacao: ?[]const u8 = null,

    const Cliente = struct {
        nome: []const u8,
        email: []const u8,
    };

    const Item = struct {
        produto: []const u8,
        quantidade: u32,
        preco_unitario: f64,
    };

    const Status = enum {
        pendente,
        processando,
        enviado,
        entregue,
    };
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const itens = [_]Pedido.Item{
        .{ .produto = "Notebook", .quantidade = 1, .preco_unitario = 4500.00 },
        .{ .produto = "Mouse", .quantidade = 2, .preco_unitario = 79.90 },
    };

    const pedido = Pedido{
        .id = 12345,
        .cliente = .{
            .nome = "João da Silva",
            .email = "joao@exemplo.com",
        },
        .itens = &itens,
        .total = 4659.80,
        .status = .processando,
        .observacao = "Entregar no período da manhã",
    };

    const json_str = try std.json.stringifyAlloc(allocator, pedido, .{
        .whitespace = .indent_2,
    });
    defer allocator.free(json_str);

    const stdout = std.io.getStdOut().writer();
    try stdout.print("{s}\n", .{json_str});
}

Exemplo 2: Serialização Direta para Writer

const std = @import("std");

const LogEntry = struct {
    timestamp: u64,
    nivel: []const u8,
    mensagem: []const u8,
    contexto: ?Contexto = null,

    const Contexto = struct {
        modulo: []const u8,
        linha: u32,
    };
};

fn emitirLog(writer: anytype, entry: LogEntry) !void {
    try std.json.stringify(entry, .{
        .emit_null_optional_fields = false,
    }, writer);
    try writer.writeByte('\n');
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // Serializa diretamente no stdout (sem alocação)
    try emitirLog(stdout, .{
        .timestamp = 1708560000,
        .nivel = "INFO",
        .mensagem = "Servidor iniciado na porta 8080",
        .contexto = null,
    });

    try emitirLog(stdout, .{
        .timestamp = 1708560001,
        .nivel = "ERROR",
        .mensagem = "Falha na conexão com o banco",
        .contexto = .{ .modulo = "database", .linha = 142 },
    });

    try emitirLog(stdout, .{
        .timestamp = 1708560005,
        .nivel = "WARN",
        .mensagem = "Memória acima de 80%",
        .contexto = .{ .modulo = "monitor", .linha = 57 },
    });
}

Exemplo 3: Construindo JSON Dinamicamente

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Serialização de diferentes tipos
    var buf = std.ArrayList(u8).init(allocator);
    defer buf.deinit();
    const writer = buf.writer();

    // Array de tipos mistos via struct
    const dados = .{
        .numeros = [_]i32{ 1, 2, 3, 4, 5 },
        .nomes = [_][]const u8{ "Ana", "Bruno", "Carla" },
        .config = .{
            .ativo = true,
            .limite = @as(u32, 100),
            .descricao = @as(?[]const u8, null),
        },
    };

    try std.json.stringify(dados, .{ .whitespace = .indent_2 }, writer);

    const stdout = std.io.getStdOut().writer();
    try stdout.print("{s}\n", .{buf.items});

    // Serialização compacta (minified)
    buf.clearRetainingCapacity();
    try std.json.stringify(dados, .{}, writer);
    try stdout.print("\nCompacto: {s}\n", .{buf.items});
}

Padrões Comuns

Omitir Campos Nulos

const opts = std.json.StringifyOptions{
    .emit_null_optional_fields = false,
};
// Campos ?T com valor null não aparecerão no JSON

JSON para Respostas HTTP

fn handleRequest(writer: anytype) !void {
    const resposta = .{
        .status = @as(u16, 200),
        .body = .{ .mensagem = "OK" },
    };
    try std.json.stringify(resposta, .{}, writer);
}

Serialização de Enums

Enums são serializadas como strings com o nome do tag:

const Status = enum { ativo, inativo, pendente };
// .ativo serializa como "ativo"

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.