Como Gerar e Serializar JSON em Zig

Introdução

Gerar JSON é tão importante quanto parseá-lo. Seja para responder a uma API, salvar configurações ou exportar dados, a serialização JSON é uma operação fundamental. Em Zig, std.json.stringify e std.json.stringifyAlloc permitem converter structs e valores Zig diretamente para JSON.

Nesta receita, você aprenderá a serializar diferentes tipos de dados para JSON.

Pré-requisitos

Serializar uma Struct Simples

Converta uma struct Zig diretamente para JSON:

const std = @import("std");

const Usuario = struct {
    nome: []const u8,
    email: []const u8,
    idade: u32,
    ativo: bool,
};

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

    const usuario = Usuario{
        .nome = "Maria Silva",
        .email = "maria@exemplo.com",
        .idade = 28,
        .ativo = true,
    };

    // Serializar para string alocada
    const json = try std.json.stringifyAlloc(allocator, usuario, .{});
    defer allocator.free(json);

    std.debug.print("{s}\n", .{json});
}

Saída esperada

{"nome":"Maria Silva","email":"maria@exemplo.com","idade":28,"ativo":true}

JSON Formatado (Pretty Print)

Para JSON legível com indentação:

const std = @import("std");

const Configuracao = struct {
    servidor: struct {
        host: []const u8,
        porta: u16,
    },
    banco_dados: struct {
        url: []const u8,
        pool_size: u32,
    },
    debug: bool,
};

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

    const config = Configuracao{
        .servidor = .{
            .host = "0.0.0.0",
            .porta = 8080,
        },
        .banco_dados = .{
            .url = "postgresql://localhost:5432/meubanco",
            .pool_size = 10,
        },
        .debug = false,
    };

    // Serializar com formatação (indentação de 4 espaços)
    const json = try std.json.stringifyAlloc(allocator, config, .{
        .whitespace = .indent_4,
    });
    defer allocator.free(json);

    std.debug.print("{s}\n", .{json});
}

Saída esperada

{
    "servidor": {
        "host": "0.0.0.0",
        "porta": 8080
    },
    "banco_dados": {
        "url": "postgresql://localhost:5432/meubanco",
        "pool_size": 10
    },
    "debug": false
}

Serializar para um Writer

Escreva JSON diretamente para qualquer writer (arquivo, buffer, rede):

const std = @import("std");

const Evento = struct {
    tipo: []const u8,
    timestamp: i64,
    dados: []const u8,
};

pub fn main() !void {
    const evento = Evento{
        .tipo = "login",
        .timestamp = 1740100000,
        .dados = "usuario=maria",
    };

    // Escrever para stdout
    const stdout = std.io.getStdOut().writer();
    try std.json.stringify(evento, .{}, stdout);
    try stdout.writeByte('\n');

    // Escrever para buffer fixo
    var buffer: [256]u8 = undefined;
    var stream = std.io.fixedBufferStream(&buffer);
    try std.json.stringify(evento, .{}, stream.writer());

    const json = stream.getWritten();
    std.debug.print("Buffer: {s}\n", .{json});
}

Serializar Arrays e Slices

Arrays e slices são automaticamente convertidos para arrays JSON:

const std = @import("std");

const Produto = struct {
    id: u64,
    nome: []const u8,
    preco: f64,
    tags: []const []const u8,
};

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

    const produtos = [_]Produto{
        .{
            .id = 1,
            .nome = "Notebook",
            .preco = 4599.90,
            .tags = &.{ "eletrônico", "computador" },
        },
        .{
            .id = 2,
            .nome = "Teclado",
            .preco = 299.90,
            .tags = &.{ "periférico", "acessório" },
        },
        .{
            .id = 3,
            .nome = "Monitor",
            .preco = 1899.90,
            .tags = &.{ "eletrônico", "display" },
        },
    };

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

    std.debug.print("{s}\n", .{json});
}

Campos Opcionais (null em JSON)

Campos opcionais ?T são serializados como null quando não tem valor:

const std = @import("std");

const Perfil = struct {
    nome: []const u8,
    bio: ?[]const u8,
    site: ?[]const u8,
    seguidores: ?u32,
};

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

    // Perfil completo
    const perfil_completo = Perfil{
        .nome = "João Dev",
        .bio = "Desenvolvedor Zig apaixonado",
        .site = "https://joaodev.com",
        .seguidores = 1500,
    };

    const json1 = try std.json.stringifyAlloc(allocator, perfil_completo, .{
        .whitespace = .indent_2,
    });
    defer allocator.free(json1);
    std.debug.print("Completo:\n{s}\n\n", .{json1});

    // Perfil mínimo (campos opcionais como null)
    const perfil_minimo = Perfil{
        .nome = "Ana",
        .bio = null,
        .site = null,
        .seguidores = null,
    };

    const json2 = try std.json.stringifyAlloc(allocator, perfil_minimo, .{
        .whitespace = .indent_2,
    });
    defer allocator.free(json2);
    std.debug.print("Mínimo:\n{s}\n", .{json2});
}

Saída esperada

Completo:
{
  "nome": "João Dev",
  "bio": "Desenvolvedor Zig apaixonado",
  "site": "https://joaodev.com",
  "seguidores": 1500
}

Mínimo:
{
  "nome": "Ana",
  "bio": null,
  "site": null,
  "seguidores": null
}

Construir JSON Dinamicamente

Para JSON com estrutura dinâmica, use std.json.Value:

const std = @import("std");

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

    // Criar objeto JSON dinamicamente
    var root = std.json.Value{ .object = std.json.ObjectMap.init(allocator) };
    defer root.object.deinit();

    try root.object.put("versao", .{ .string = "1.0.0" });
    try root.object.put("nome", .{ .string = "meu-app" });
    try root.object.put("porta", .{ .integer = 3000 });

    // Criar sub-objeto
    var deps = std.json.ObjectMap.init(allocator);
    defer deps.deinit();
    try deps.put("std", .{ .string = "0.13.0" });
    try root.object.put("dependencias", .{ .object = deps });

    // Serializar
    const json = try std.json.stringifyAlloc(allocator, root, .{
        .whitespace = .indent_2,
    });
    defer allocator.free(json);

    std.debug.print("{s}\n", .{json});
}

Dicas e Boas Práticas

  1. Use structs para JSON estático: Quando a estrutura é conhecida, structs são mais eficientes e seguras.

  2. Escolha a formatação: Use .whitespace = .indent_2 ou .indent_4 para arquivos de configuração legíveis. Sem whitespace para dados de rede.

  3. Writers para streaming: Use std.json.stringify com um writer para evitar alocações grandes.

  4. Campos opcionais: Use ?T para campos que podem ser null no JSON.

  5. Combine com HTTP: Use a serialização para enviar dados via HTTP POST.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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