Como Parsear JSON em Zig

Introdução

JSON (JavaScript Object Notation) é o formato de dados mais usado para APIs web, configuração e troca de dados entre sistemas. Em Zig, a biblioteca padrão std.json oferece ferramentas poderosas para parsear JSON de forma tipada ou dinâmica.

Nesta receita, você aprenderá as diversas formas de parsear JSON em Zig, desde parsing simples até cenários complexos com dados aninhados.

Pré-requisitos

Parsing Tipado (Recomendado)

A forma mais segura: defina uma struct que espelha o JSON esperado:

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 json_str =
        \\{
        \\  "nome": "Maria Silva",
        \\  "email": "maria@exemplo.com",
        \\  "idade": 28,
        \\  "ativo": true
        \\}
    ;

    const parsed = try std.json.parseFromSlice(
        Usuario,
        allocator,
        json_str,
        .{},
    );
    defer parsed.deinit();

    const user = parsed.value;
    std.debug.print("Nome: {s}\n", .{user.nome});
    std.debug.print("Email: {s}\n", .{user.email});
    std.debug.print("Idade: {d}\n", .{user.idade});
    std.debug.print("Ativo: {}\n", .{user.ativo});
}

Saída esperada

Nome: Maria Silva
Email: maria@exemplo.com
Idade: 28
Ativo: true

Parsing com Campos Opcionais

Use tipos opcionais para campos que podem não existir no JSON:

const std = @import("std");

const Produto = struct {
    nome: []const u8,
    preco: f64,
    descricao: ?[]const u8 = null,
    estoque: ?u32 = null,
    tags: ?[]const []const u8 = null,
};

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

    // JSON sem campos opcionais
    const json_minimal =
        \\{"nome": "Teclado Mecânico", "preco": 299.90}
    ;

    const p1 = try std.json.parseFromSlice(Produto, allocator, json_minimal, .{
        .ignore_unknown_fields = true,
    });
    defer p1.deinit();

    std.debug.print("Produto: {s}\n", .{p1.value.nome});
    std.debug.print("Preço: {d:.2}\n", .{p1.value.preco});
    std.debug.print("Descrição: {s}\n", .{p1.value.descricao orelse "N/A"});

    // JSON completo
    const json_full =
        \\{
        \\  "nome": "Mouse Gamer",
        \\  "preco": 159.90,
        \\  "descricao": "Mouse com 6 botões programáveis",
        \\  "estoque": 42,
        \\  "tags": ["gamer", "periférico", "rgb"]
        \\}
    ;

    const p2 = try std.json.parseFromSlice(Produto, allocator, json_full, .{
        .ignore_unknown_fields = true,
    });
    defer p2.deinit();

    std.debug.print("\nProduto: {s}\n", .{p2.value.nome});
    std.debug.print("Descrição: {s}\n", .{p2.value.descricao.?});
    std.debug.print("Estoque: {d}\n", .{p2.value.estoque.?});
    if (p2.value.tags) |tags| {
        std.debug.print("Tags: ", .{});
        for (tags, 0..) |tag, i| {
            if (i > 0) std.debug.print(", ", .{});
            std.debug.print("{s}", .{tag});
        }
        std.debug.print("\n", .{});
    }
}

Parsing com Ignorar Campos Desconhecidos

Quando o JSON contém campos que você não precisa:

const std = @import("std");

const Resumo = struct {
    id: u64,
    titulo: []const u8,
};

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

    // JSON com muitos campos que não precisamos
    const json_str =
        \\{
        \\  "id": 12345,
        \\  "titulo": "Artigo sobre Zig",
        \\  "autor": "João",
        \\  "data_publicacao": "2026-02-21",
        \\  "conteudo": "Lorem ipsum...",
        \\  "comentarios": 42,
        \\  "visualizacoes": 1500
        \\}
    ;

    // ignore_unknown_fields permite ignorar campos extras
    const parsed = try std.json.parseFromSlice(Resumo, allocator, json_str, .{
        .ignore_unknown_fields = true,
    });
    defer parsed.deinit();

    std.debug.print("ID: {d}\n", .{parsed.value.id});
    std.debug.print("Título: {s}\n", .{parsed.value.titulo});
}

Parsing Dinâmico (sem tipo)

Quando a estrutura JSON é desconhecida em tempo de compilação:

const std = @import("std");

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

    const json_str =
        \\{
        \\  "nome": "Zig Brasil",
        \\  "membros": 1500,
        \\  "ativo": true,
        \\  "linguagens": ["zig", "c", "c++"],
        \\  "contato": {
        \\    "email": "contato@zigbrasil.com",
        \\    "site": "https://zigbrasil.com"
        \\  }
        \\}
    ;

    const parsed = try std.json.parseFromSlice(
        std.json.Value,
        allocator,
        json_str,
        .{},
    );
    defer parsed.deinit();

    const root = parsed.value.object;

    // Acessar string
    const nome = root.get("nome").?.string;
    std.debug.print("Nome: {s}\n", .{nome});

    // Acessar número
    const membros = root.get("membros").?.integer;
    std.debug.print("Membros: {d}\n", .{membros});

    // Acessar boolean
    const ativo = root.get("ativo").?.bool;
    std.debug.print("Ativo: {}\n", .{ativo});

    // Acessar array
    const linguagens = root.get("linguagens").?.array;
    std.debug.print("Linguagens: ", .{});
    for (linguagens.items, 0..) |item, i| {
        if (i > 0) std.debug.print(", ", .{});
        std.debug.print("{s}", .{item.string});
    }
    std.debug.print("\n", .{});

    // Acessar objeto aninhado
    const contato = root.get("contato").?.object;
    const email = contato.get("email").?.string;
    std.debug.print("Email: {s}\n", .{email});
}

Parsing de Array JSON

Parsear um array de objetos:

const std = @import("std");

const Tarefa = struct {
    id: u64,
    titulo: []const u8,
    completa: bool,
};

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

    const json_str =
        \\[
        \\  {"id": 1, "titulo": "Estudar Zig", "completa": true},
        \\  {"id": 2, "titulo": "Criar projeto", "completa": false},
        \\  {"id": 3, "titulo": "Escrever testes", "completa": false}
        \\]
    ;

    const parsed = try std.json.parseFromSlice(
        []Tarefa,
        allocator,
        json_str,
        .{},
    );
    defer parsed.deinit();

    const tarefas = parsed.value;
    std.debug.print("Total de tarefas: {d}\n\n", .{tarefas.len});

    for (tarefas) |tarefa| {
        const status = if (tarefa.completa) "[x]" else "[ ]";
        std.debug.print("{s} #{d}: {s}\n", .{ status, tarefa.id, tarefa.titulo });
    }
}

Saída esperada

Total de tarefas: 3

[x] #1: Estudar Zig
[ ] #2: Criar projeto
[ ] #3: Escrever testes

Tratamento de Erros no Parsing

Trate erros de JSON malformado:

const std = @import("std");

const Config = struct {
    porta: u16,
    host: []const u8,
};

fn parseConfig(allocator: std.mem.Allocator, json_str: []const u8) !Config {
    const parsed = std.json.parseFromSlice(Config, allocator, json_str, .{}) catch |err| {
        switch (err) {
            error.UnexpectedCharacter => {
                std.debug.print("Erro: JSON contém caractere inesperado\n", .{});
                return err;
            },
            error.InvalidNumber => {
                std.debug.print("Erro: número inválido no JSON\n", .{});
                return err;
            },
            else => {
                std.debug.print("Erro ao parsear JSON: {}\n", .{err});
                return err;
            },
        }
    };
    defer parsed.deinit();

    // Copiar strings para que sobrevivam após deinit
    const host = try allocator.dupe(u8, parsed.value.host);

    return Config{
        .porta = parsed.value.porta,
        .host = host,
    };
}

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

    // JSON válido
    const valid = try parseConfig(allocator, "{\"porta\": 8080, \"host\": \"localhost\"}");
    defer allocator.free(valid.host);
    std.debug.print("Config: {s}:{d}\n", .{ valid.host, valid.porta });

    // JSON inválido
    _ = parseConfig(allocator, "{porta: 8080}") catch {
        std.debug.print("Falha ao parsear JSON inválido (esperado)\n", .{});
    };
}

Dicas e Boas Práticas

  1. Prefira parsing tipado: Use structs quando a estrutura é conhecida. É mais seguro e performático.

  2. Use ignore_unknown_fields: Ao consumir APIs externas, ignore campos que você não precisa.

  3. Campos opcionais com valor padrão: Use ?T = null para campos que podem não existir.

  4. Libere memória: Sempre chame parsed.deinit() para liberar a memória alocada pelo parser.

  5. Cuidado com strings: As strings do JSON parseado são referências ao buffer original ou à memória do parsed. Use allocator.dupe se precisar mantê-las após deinit().

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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