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

std.json Parsing — Desserialização de JSON

O sistema de parsing JSON do Zig permite converter strings JSON em valores Zig de forma segura e eficiente. Ele suporta tanto parsing tipado (diretamente para structs) quanto parsing dinâmico (para json.Value), além de um parser de streaming para processamento incremental de documentos grandes.

Visão Geral

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

Abordagens de Parsing

  1. parseFromSlice(T, ...) — Converte JSON diretamente em um tipo Zig T
  2. parseFromSlice(json.Value, ...) — Retorna uma árvore dinâmica navegável
  3. Scanner/Reader — Parsing token a token para streaming

Funções Principais

// Parse tipado ou dinâmico a partir de um slice
pub fn parseFromSlice(
    comptime T: type,
    allocator: Allocator,
    source: []const u8,
    options: ParseOptions,
) !Parsed(T)

// Parse a partir de um Reader (streaming)
pub fn parseFromTokenSource(
    comptime T: type,
    allocator: Allocator,
    source: anytype,
    options: ParseOptions,
) !Parsed(T)

// Scanner de baixo nível
pub fn Scanner = struct {
    pub fn next(self: *Scanner) !Token
    // ...
};

ParseOptions

pub const ParseOptions = struct {
    // Ignora campos JSON que não existem no tipo T
    ignore_unknown_fields: bool = true,

    // Controle de alocação
    allocate: enum { alloc_always, alloc_if_needed } = .alloc_if_needed,

    // Tamanho máximo de aninhamento
    max_nesting_depth: ?usize = null,
};

Exemplo 1: Parsing de API REST

const std = @import("std");

const ApiResponse = struct {
    success: bool,
    data: ?Data = null,
    error_msg: ?[]const u8 = null,

    const Data = struct {
        usuarios: []const Usuario,
        pagina: u32,
        total_paginas: u32,
    };

    const Usuario = struct {
        id: u64,
        nome: []const u8,
        email: []const u8,
        admin: bool = false,
    };
};

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

    const json_str =
        \\{
        \\  "success": true,
        \\  "data": {
        \\    "usuarios": [
        \\      {"id": 1, "nome": "Ana", "email": "ana@ex.com", "admin": true},
        \\      {"id": 2, "nome": "Bruno", "email": "bruno@ex.com"},
        \\      {"id": 3, "nome": "Carla", "email": "carla@ex.com"}
        \\    ],
        \\    "pagina": 1,
        \\    "total_paginas": 5
        \\  }
        \\}
    ;

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

    const resp = parsed.value;
    const stdout = std.io.getStdOut().writer();

    try stdout.print("Sucesso: {}\n", .{resp.success});

    if (resp.data) |data| {
        try stdout.print("Página {d}/{d}\n", .{ data.pagina, data.total_paginas });
        try stdout.writeAll("\nUsuários:\n");
        for (data.usuarios) |user| {
            const tag = if (user.admin) " [ADMIN]" else "";
            try stdout.print("  {d}. {s} ({s}){s}\n", .{
                user.id, user.nome, user.email, tag,
            });
        }
    }
}

Exemplo 2: Parsing Dinâmico com Navegação

const std = @import("std");

fn acessarCampo(value: std.json.Value, caminho: []const []const u8) ?std.json.Value {
    var atual = value;
    for (caminho) |chave| {
        switch (atual) {
            .object => |obj| {
                if (obj.get(chave)) |proximo| {
                    atual = proximo;
                } else {
                    return null;
                }
            },
            else => return null,
        }
    }
    return atual;
}

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

    const json_str =
        \\{
        \\  "app": {
        \\    "nome": "MeuApp",
        \\    "versao": "2.1.0",
        \\    "config": {
        \\      "debug": false,
        \\      "max_usuarios": 1000,
        \\      "features": ["auth", "cache", "logs"]
        \\    }
        \\  }
        \\}
    ;

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

    const root = parsed.value;
    const stdout = std.io.getStdOut().writer();

    // Acesso profundo
    const caminho_nome = [_][]const u8{ "app", "nome" };
    if (acessarCampo(root, &caminho_nome)) |nome| {
        try stdout.print("App: {s}\n", .{nome.string});
    }

    const caminho_max = [_][]const u8{ "app", "config", "max_usuarios" };
    if (acessarCampo(root, &caminho_max)) |max| {
        try stdout.print("Max usuários: {d}\n", .{max.integer});
    }

    // Lista de features
    const caminho_features = [_][]const u8{ "app", "config", "features" };
    if (acessarCampo(root, &caminho_features)) |features| {
        try stdout.writeAll("Features: ");
        for (features.array.items, 0..) |item, i| {
            if (i > 0) try stdout.writeAll(", ");
            try stdout.writeAll(item.string);
        }
        try stdout.writeAll("\n");
    }
}

Exemplo 3: Tratamento de Erros e Validação

const std = @import("std");

const Produto = struct {
    nome: []const u8,
    preco: f64,
    quantidade: u32,
    categoria: []const u8 = "geral",
};

fn parseProdutos(json_str: []const u8, allocator: std.mem.Allocator) ![]const Produto {
    const parsed = try std.json.parseFromSlice(
        struct { produtos: []const Produto },
        allocator,
        json_str,
        .{ .ignore_unknown_fields = true },
    );
    // Nota: o caller é responsável por chamar parsed.deinit()
    return parsed.value.produtos;
}

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

    const stdout = std.io.getStdOut().writer();

    // JSON válido
    const valido =
        \\{"produtos": [
        \\  {"nome": "Café", "preco": 12.50, "quantidade": 100},
        \\  {"nome": "Chá", "preco": 8.90, "quantidade": 50, "categoria": "bebidas"}
        \\]}
    ;

    const parsed = std.json.parseFromSlice(
        struct { produtos: []const Produto },
        allocator,
        valido,
        .{ .ignore_unknown_fields = true },
    );

    if (parsed) |p| {
        defer p.deinit();
        try stdout.writeAll("Produtos carregados:\n");
        for (p.value.produtos) |prod| {
            try stdout.print("  {s}: R${d:.2} (x{d}) [{s}]\n", .{
                prod.nome, prod.preco, prod.quantidade, prod.categoria,
            });
        }
    } else |err| {
        try stdout.print("Erro ao parsear JSON: {}\n", .{err});
    }

    // JSON inválido — tratamento de erro
    const invalido = "{ invalido }";
    if (std.json.parseFromSlice(std.json.Value, allocator, invalido, .{})) |_| {
        try stdout.writeAll("Inesperado: parsing bem-sucedido\n");
    } else |err| {
        try stdout.print("Erro esperado em JSON inválido: {}\n", .{err});
    }
}

Padrões Comuns

Campos Opcionais com Valores Padrão

const Config = struct {
    porta: u16 = 8080,       // padrão se ausente no JSON
    host: []const u8 = "localhost",
    debug: bool = false,
};

Ignorar Campos Desconhecidos

const parsed = try std.json.parseFromSlice(
    MeuTipo,
    allocator,
    json_str,
    .{ .ignore_unknown_fields = true },
);

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

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