JSON em Zig 0.16: parseFromSlice, Value e deinit

JSON é o formato de dados mais popular da web moderna. APIs REST, configurações, logs e troca de dados entre sistemas usam JSON. Saber trabalhar com JSON é uma habilidade essencial para qualquer desenvolvedor Zig.

Como parsear JSON em Zig 0.16.0? Use std.json.parseFromSlice(T, allocator, bytes, options), leia parsed.value e registre defer parsed.deinit(). O retorno std.json.Parsed(T) possui uma arena interna. Por padrão, strings podem apontar para o buffer de entrada: mantenha esse buffer vivo ou use .allocate = .alloc_always para copiá-las para a arena. Para JSON sem schema fixo, use T = std.json.Value e confira as tags antes de acessar os campos.

const parsed = try std.json.parseFromSlice(User, allocator, json_bytes, .{
    .ignore_unknown_fields = true,
    .duplicate_field_behavior = .error,
});
defer parsed.deinit();

const user = parsed.value;

Neste tutorial, você vai aprender parsing JSON em Zig — desde o uso correto de parseFromSlice, deinit, page_allocator e ParseOptions até serialização, JSON dinâmico e APIs reais.

Por que JSON em Zig?

O Zig oferece um sistema de parsing JSON poderoso e eficiente através do módulo std.json:

Característicastd.json ZigOutras Linguagens
Zero allocations✅ Possível⚠️ Raro
Type safety✅ Compile-time⚠️ Runtime
Performance✅ NativaVariável
Streaming✅ Suportado⚠️ Limitado
Sem dependências✅ Built-in❌ Precisa libs

Abordagens do std.json

  1. Structs tipadas — Parse direto para structs (mais eficiente, type safe)
  2. Value dinâmico — Trabalhe com JSON genérico (flexível, menos type safe)
  3. Streaming — Parse grandes arquivos sem carregar tudo na memória

Vamos explorar cada uma.

Pré-requisitos

Antes de começar:

  1. Zig instalado (estável 0.16.0) — veja Como Instalar o Zig
  2. Conhecimento básico de Zig — structs, allocators, error handling
  3. Familiaridade com JSON — objetos, arrays, tipos de dados

Setup do Projeto

mkdir json-tutorial
cd json-tutorial
zig init

Parte 1: Parsing JSON Básico

1.1 Parse para Struct (Mais Comum)

A forma mais eficiente de trabalhar com JSON é mapear diretamente para structs:

// src/main.zig
const std = @import("std");

// Struct que representa o JSON
const User = struct {
    id: u32,
    name: []const u8,
    email: []const u8,
    active: bool,
};

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

    // JSON de exemplo
    const json_str =
        \\{
        \\  "id": 1,
        \\  "name": "João Silva",
        \\  "email": "[email protected]",
        \\  "active": true
        \\}
    ;

    // Parse o JSON para a struct User
    const parsed = try std.json.parseFromSlice(
        User,
        allocator,
        json_str,
        .{}, // opções padrão
    );
    // parsed.value pode apontar para memória pertencente a parsed.
    // Portanto, mantenha o deinit no mesmo escopo do uso do valor.
    defer parsed.deinit();

    // Acessa os dados
    const user = parsed.value;
    std.debug.print("ID: {d}\n", .{user.id});
    std.debug.print("Nome: {s}\n", .{user.name});
    std.debug.print("Email: {s}\n", .{user.email});
    std.debug.print("Ativo: {}\n", .{user.active});
}

Compile e execute:

zig build run

Saída:

ID: 1
Nome: João Silva
Email: [email protected]
Ativo: true

1.2 Parsed, arena e tempo de vida: quem libera o quê?

Conforme o código de std.json na versão 0.16.0, std.json.Parsed(T) contém value: T e arena: *std.heap.ArenaAllocator. parseFromSlice cria essa arena usando o allocator recebido; parsed.deinit() libera as alocações da arena e destrói o objeto que a controla. Não libera o buffer JSON fornecido pelo chamador.

Há dois tempos de vida a observar:

RecursoQuem gerenciaAté quando deve permanecer válido?
Buffer JSON de entradaChamadorAté o último uso de strings que apontem para ele
Strings copiadas, arrays e objetos do parseArena de ParsedAté parsed.deinit()
Cópia feita com allocator.dupeChamadorAté seu próprio allocator.free()

Com .alloc_if_needed (padrão de parseFromSlice), o parser pode emprestar strings sem escapes diretamente do buffer. Strings que exigem decodificação podem ser alocadas na arena. Assim, manter parsed vivo não garante que o buffer de entrada continue vivo. Use .alloc_always quando uma resposta HTTP, por exemplo, será liberada antes do último uso do resultado.

Não faça allocator.free(parsed.value.name): esse slice pode pertencer à entrada ou à arena, e não ser uma alocação independente. Também não retorne apenas parsed.value de uma função que executa defer parsed.deinit(). Retorne o Parsed(T) inteiro e transfira a responsabilidade de chamar deinit, ou copie os dados necessários antes de liberar o resultado.

Testes reproduzíveis de strings emprestadas e copiadas

Salve o bloco abaixo como json-lifetime.zig. Estes três testes foram executados com Zig 0.16.0; std.testing.allocator também verifica vazamentos nas alocações usadas pelos testes.

const std = @import("std");
const User = struct { name: []const u8 };

test "alloc_if_needed pode emprestar a string da entrada" {
    var input = [_]u8{ '{', '"', 'n', 'a', 'm', 'e', '"', ':', '"', 'A', 'n', 'a', '"', '}' };
    const parsed = try std.json.parseFromSlice(User, std.testing.allocator, &input, .{});
    defer parsed.deinit();

    // A string sem escapes aponta para dentro de input.
    try std.testing.expect(parsed.value.name.ptr == input[9..12].ptr);
    try std.testing.expectEqualStrings("Ana", parsed.value.name);
}

test "alloc_always permite liberar a entrada antes do resultado" {
    const allocator = std.testing.allocator;
    const parsed = blk: {
        const input = try allocator.dupe(u8, "{\"name\":\"Ana\"}");
        defer allocator.free(input);
        break :blk try std.json.parseFromSlice(User, allocator, input, .{
            .allocate = .alloc_always,
        });
    };
    defer parsed.deinit();
    try std.testing.expectEqualStrings("Ana", parsed.value.name);
}

test "std.json.Value com verificacao de tags" {
    const parsed = try std.json.parseFromSlice(
        std.json.Value,
        std.testing.allocator,
        "{\"name\":\"Ana\"}",
        .{ .allocate = .alloc_always },
    );
    defer parsed.deinit();

    const object = switch (parsed.value) {
        .object => |obj| obj,
        else => return error.ExpectedObject,
    };
    const name = object.get("name") orelse return error.MissingName;
    switch (name) {
        .string => |text| try std.testing.expectEqualStrings("Ana", text),
        else => return error.ExpectedString,
    }
}

Execute:

zig version # confira 0.16.0
zig test json-lifetime.zig

Resultado esperado: All 3 tests passed. No JSON dinâmico, a checagem de tags evita acessar .object ou .string quando a entrada contém outro tipo. Usar .?.string diretamente pressupõe tanto a existência do campo quanto seu tipo.

page_allocator, DebugAllocator e uma arena externa

std.heap.page_allocator libera memória, inclusive quando a arena interna de Parsed chama seu método de liberação. Não é correto afirmar que o parse repetido necessariamente acumula memória com esse allocator. Entretanto, ele não oferece os diagnósticos de vazamento do std.heap.DebugAllocator(.{}), nome usado no Zig 0.16.0 em lugar do antigo GeneralPurposeAllocator.

Se você já controla uma arena por requisição, pode usar std.json.parseFromSliceLeaky(T, arena.allocator(), bytes, options). Essa variante retorna T, não Parsed(T): não há parsed.deinit(); a limpeza fica a cargo de arena.deinit() ou do reset da arena. O nome Leaky não significa que uma arena bem gerenciada vaze, e sim que a função não mantém um objeto separado para limpar suas alocações. A regra do buffer emprestado continua valendo.

Veja também o guia de arena allocators e a referência de std.json. Escopo de compatibilidade: os testes desta seção foram verificados em 0.16.0; os exemplos históricos de HTTP, build e serialização abaixo exigem revisão de APIs ao migrar de versões anteriores.

1.3 Opções de parseFromSlice

O último argumento controla como o JSON será convertido:

OpçãoQuando usar
.allocate = .alloc_if_neededPadrão: copia dados apenas quando o parser precisa
.allocate = .alloc_alwaysForça a cópia das strings para a memória do resultado
.ignore_unknown_fields = trueAceita campos extras que não existem na struct Zig
.duplicate_field_behavior = .errorRejeita chaves repetidas em objetos JSON
.duplicate_field_behavior = .use_firstConserva a primeira ocorrência de uma chave repetida
.duplicate_field_behavior = .use_lastConserva a última ocorrência de uma chave repetida

Para dados externos, rejeitar campos duplicados evita interpretações ambíguas entre seu programa e outros componentes:

const parsed = try std.json.parseFromSlice(User, allocator, json_str, .{
    .ignore_unknown_fields = true,
    .duplicate_field_behavior = .error,
});
defer parsed.deinit();

ignore_unknown_fields e duplicate_field_behavior resolvem problemas diferentes: o primeiro trata nomes que a struct não declara; o segundo trata o mesmo nome repetido no objeto recebido.

Exemplo concreto com chave duplicada — útil quando a entrada vem de um cliente HTTP ou de um arquivo editado à mão:

const ambiguous =
    \{
    \  "id": 1,
    \  "name": "primeiro",
    \  "name": "segundo"
    \}
;

// .error: rejeita o objeto ambíguo.
_ = std.json.parseFromSlice(User, allocator, ambiguous, .{
    .duplicate_field_behavior = .error,
}) catch |err| {
    std.debug.print("duplicata rejeitada: {}\n", .{err});
};

// .use_last: aceita e fica com "segundo".
const parsed_last = try std.json.parseFromSlice(User, allocator, ambiguous, .{
    .duplicate_field_behavior = .use_last,
});
defer parsed_last.deinit();
std.debug.print("name={s}\n", .{parsed_last.value.name});

1.4 Parse de Arrays

JSON com arrays é igualmente simples:

const std = @import("std");

const Product = struct {
    id: u32,
    name: []const u8,
    price: f64,
    tags: []const []const u8, // Array de strings
};

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

    const json_str =
        \\[
        \\  {"id": 1, "name": "Notebook", "price": 4500.00, "tags": ["eletrônicos", "computadores"]},
        \\  {"id": 2, "name": "Mouse", "price": 89.90, "tags": ["eletrônicos", "acessórios"]},
        \\  {"id": 3, "name": "Teclado", "price": 199.00, "tags": ["eletrônicos"]}
        \\]
    ;

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

    std.debug.print("Produtos encontrados: {d}\n\n", .{parsed.value.len});

    for (parsed.value) |product| {
        std.debug.print("📦 {s}\n", .{product.name});
        std.debug.print("   Preço: R$ {d:.2}\n", .{product.price});
        std.debug.print("   Tags: ", .{});
        for (product.tags) |tag| {
            std.debug.print("{s} ", .{tag});
        }
        std.debug.print("\n\n", .{});
    }
}

1.5 Campos Opcionais

Muitas APIs retornam campos que podem estar ausentes. Use ?T para opcional:

const std = @import("std");

const Profile = struct {
    username: []const u8,
    bio: ?[]const u8 = null,        // Ausente ou null
    website: ?[]const u8 = null,    // Ausente ou null
    followers: u32,
    following: u32,
};

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

    // JSON com campos ausentes
    const json_str =
        \\{
        \\  "username": "maria_dev",
        \\  "bio": "Desenvolvedora Zig entusiasta",
        \\  "followers": 1500,
        \\  "following": 230
        \\}
    ;

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

    const profile = parsed.value;
    std.debug.print("👤 @{s}\n", .{profile.username});

    if (profile.bio) |bio| {
        std.debug.print("   Bio: {s}\n", .{bio});
    } else {
        std.debug.print("   Bio: (não informada)\n", .{});
    }

    if (profile.website) |site| {
        std.debug.print("   Site: {s}\n", .{site});
    } else {
        std.debug.print("   Site: (não informado)\n", .{});
    }

    std.debug.print("   Seguidores: {d} | Seguindo: {d}\n", .{
        profile.followers,
        profile.following,
    });
}

1.6 Nomes de campos

A forma direta é declarar na struct o mesmo nome usado no JSON (por exemplo, user_id ou created_at):

const std = @import("std");

const ApiResponse = struct {
    // Mapeia "user_id" no JSON para "user_id" na struct
    user_id: u32,
    // Mapeia "created_at" no JSON para "created_at" na struct
    created_at: []const u8,
    // O JSON também deve usar "is_active".
    is_active: bool,
};

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

    // JSON com snake_case
    const json_str =
        \\{
        \\  "user_id": 42,
        \\  "created_at": "2026-02-10T14:30:00Z",
        \\  "is_active": true
        \\}
    ;

    const parsed = try std.json.parseFromSlice(
        ApiResponse,
        allocator,
        json_str,
        .{
            .allocate = .alloc_always, // Força a cópia das strings
        },
    );
    defer parsed.deinit();

    const data = parsed.value;
    std.debug.print("User ID: {d}\n", .{data.user_id});
    std.debug.print("Created: {s}\n", .{data.created_at});
    std.debug.print("Active: {}\n", .{data.is_active});
}

Parte 2: Serialização (Struct → JSON)

2.1 Serialize Struct para JSON

Converter dados para JSON é igualmente simples:

const std = @import("std");

const Task = struct {
    id: u32,
    title: []const u8,
    completed: bool,
    priority: []const u8,
};

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

    const task = Task{
        .id = 1,
        .title = "Implementar parsing JSON",
        .completed = false,
        .priority = "high",
    };

    // Serializa para string JSON
    const json_str = try std.json.stringifyAlloc(allocator, task, .{});
    defer allocator.free(json_str);

    std.debug.print("JSON gerado:\n{s}\n", .{json_str});
}

Saída:

{"id":1,"title":"Implementar parsing JSON","completed":false,"priority":"high"}

2.2 Pretty Print (JSON Formatado)

Para output legível, use .whitespace:

const std = @import("std");

const Project = struct {
    name: []const u8,
    version: []const u8,
    dependencies: []const []const u8,
};

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

    const project = Project{
        .name = "meu-app-zig",
        .version = "1.0.0",
        .dependencies = &.{ "zig-clap", "zig-json" },
    };

    // Pretty print com indentação
    const json_str = try std.json.stringifyAlloc(
        allocator,
        project,
        .{ .whitespace = .indent_2 },
    );
    defer allocator.free(json_str);

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

Saída:

{
  "name": "meu-app-zig",
  "version": "1.0.0",
  "dependencies": [
    "zig-clap",
    "zig-json"
  ]
}

2.3 Escrever JSON em Arquivo

const std = @import("std");

const Config = struct {
    server_host: []const u8,
    server_port: u16,
    debug_mode: bool,
};

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

    const config = Config{
        .server_host = "localhost",
        .server_port = 8080,
        .debug_mode = true,
    };

    // Cria arquivo
    const file = try std.fs.cwd().createFile("config.json", .{});
    defer file.close();

    // Escreve JSON formatado no arquivo
    try std.json.stringify(config, .{ .whitespace = .indent_2 }, file.writer());

    std.debug.print("✅ Configuração salva em config.json\n", .{});
}

Parte 3: JSON Dinâmico (std.json.Value)

Quando você não conhece a estrutura do JSON antecipadamente, use std.json.Value:

3.1 Trabalhando com JSON Genérico

const std = @import("std");

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

    const json_str =
        \\{
        \\  "status": "success",
        \\  "data": {
        \\    "users": [
        \\      {"id": 1, "name": "Alice"},
        \\      {"id": 2, "name": "Bob"}
        \\    ],
        \\    "total": 2
        \\  },
        \\  "meta": {
        \\    "page": 1,
        \\    "per_page": 10
        \\  }
        \\}
    ;

    // Parse para Value (JSON dinâmico)
    const parsed = try std.json.parseFromSlice(
        std.json.Value,
        allocator,
        json_str,
        .{},
    );
    defer parsed.deinit();

    const root = parsed.value;

    // Acessa campos dinamicamente
    const status = root.object.get("status").?.string;
    std.debug.print("Status: {s}\n", .{status});

    const data = root.object.get("data").?;
    const total = data.object.get("total").?.integer;
    std.debug.print("Total de usuários: {d}\n", .{total});

    const users = data.object.get("users").?.array;
    std.debug.print("Usuários:\n", .{});
    for (users.items) |user| {
        const id = user.object.get("id").?.integer;
        const name = user.object.get("name").?.string;
        std.debug.print("  - {d}: {s}\n", .{ id, name });
    }
}

3.2 Modificar JSON Dinâmico

const std = @import("std");

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

    // Cria um objeto JSON programaticamente
    var root = std.json.ObjectMap.init(allocator);
    defer root.deinit();

    // Adiciona campos
    try root.put("name", std.json.Value{ .string = "Meu App" });
    try root.put("version", std.json.Value{ .string = "1.0.0" });
    try root.put("count", std.json.Value{ .integer = 42 });

    // Cria array
    var tags = std.json.Array.init(allocator);
    try tags.append(std.json.Value{ .string = "zig" });
    try tags.append(std.json.Value{ .string = "tutorial" });
    try root.put("tags", std.json.Value{ .array = tags });

    // Cria objeto aninhado
    var config = std.json.ObjectMap.init(allocator);
    try config.put("debug", std.json.Value{ .bool = true });
    try config.put("port", std.json.Value{ .integer = 8080 });
    try root.put("config", std.json.Value{ .object = config });

    // Serializa
    const value = std.json.Value{ .object = root };
    const json_str = try std.json.stringifyAlloc(allocator, value, .{ .whitespace = .indent_2 });
    defer allocator.free(json_str);

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

Parte 4: Exemplo Real — Cliente de API

Vamos criar um cliente completo para uma API REST:

// src/api_client.zig
const std = @import("std");
const http = std.http;

// Tipos da API
pub const Post = struct {
    userId: u32,
    id: u32,
    title: []const u8,
    body: []const u8,
};

pub const CreatePostRequest = struct {
    userId: u32,
    title: []const u8,
    body: []const u8,
};

pub const ApiClient = struct {
    allocator: std.mem.Allocator,
    base_url: []const u8,
    client: http.Client,

    pub fn init(allocator: std.mem.Allocator, base_url: []const u8) ApiClient {
        return .{
            .allocator = allocator,
            .base_url = base_url,
            .client = http.Client{ .allocator = allocator },
        };
    }

    pub fn deinit(self: *ApiClient) void {
        self.client.deinit();
    }

    /// GET /posts/{id}
    pub fn getPost(self: *ApiClient, id: u32) !Post {
        const url = try std.fmt.allocPrint(
            self.allocator,
            "{s}/posts/{d}",
            .{ self.base_url, id },
        );
        defer self.allocator.free(url);

        const response = try self.makeRequest(.GET, url, null);
        defer self.allocator.free(response);

        const parsed = try std.json.parseFromSlice(
            Post,
            self.allocator,
            response,
            .{},
        );
        defer parsed.deinit();

        // Copia os dados para retornar (strings são alocadas)
        return Post{
            .userId = parsed.value.userId,
            .id = parsed.value.id,
            .title = try self.allocator.dupe(u8, parsed.value.title),
            .body = try self.allocator.dupe(u8, parsed.value.body),
        };
    }

    /// POST /posts
    pub fn createPost(self: *ApiClient, request: CreatePostRequest) !Post {
        const url = try std.fmt.allocPrint(
            self.allocator,
            "{s}/posts",
            .{self.base_url},
        );
        defer self.allocator.free(url);

        // Serializa request
        const body = try std.json.stringifyAlloc(self.allocator, request, .{});
        defer self.allocator.free(body);

        const response = try self.makeRequest(.POST, url, body);
        defer self.allocator.free(response);

        const parsed = try std.json.parseFromSlice(
            Post,
            self.allocator,
            response,
            .{},
        );
        defer parsed.deinit();

        return Post{
            .userId = parsed.value.userId,
            .id = parsed.value.id,
            .title = try self.allocator.dupe(u8, parsed.value.title),
            .body = try self.allocator.dupe(u8, parsed.value.body),
        };
    }

    fn makeRequest(
        self: *ApiClient,
        method: http.Method,
        url: []const u8,
        body: ?[]const u8,
    ) ![]const u8 {
        const uri = try std.Uri.parse(url);

        var server_header_buffer: [4096]u8 = undefined;
        var request = try self.client.open(
            method,
            uri,
            .{
                .server_header_buffer = &server_header_buffer,
                .headers = .{
                    .content_type = if (body != null) .{ .override = "application/json" } else null,
                },
            },
        );
        defer request.deinit();

        // Escreve body se presente
        if (body) |b| {
            request.transfer_encoding = .{ .content_length = b.len };
            try request.send();
            try request.writeAll(b);
        } else {
            request.transfer_encoding = .chunked;
            try request.send();
        }

        try request.finish();
        try request.wait();

        // Lê resposta
        const response_body = try request.reader().readAllAlloc(
            self.allocator,
            1024 * 1024,
        );

        return response_body;
    }
};

Usando o Cliente

// src/main.zig
const std = @import("std");
const ApiClient = @import("api_client.zig").ApiClient;
const CreatePostRequest = @import("api_client.zig").CreatePostRequest;

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

    // Inicializa cliente (usando JSONPlaceholder para testes)
    var client = ApiClient.init(allocator, "https://jsonplaceholder.typicode.com");
    defer client.deinit();

    std.debug.print("=== Testando API ===\n\n", .{});

    // GET post por ID
    std.debug.print("📥 Buscando post #1...\n", .{});
    const post = try client.getPost(1);
    defer {
        allocator.free(post.title);
        allocator.free(post.body);
    }

    std.debug.print("✅ Post encontrado:\n", .{});
    std.debug.print("   Título: {s}\n", .{post.title});
    std.debug.print("   User ID: {d}\n\n", .{post.userId});

    // POST cria novo post
    std.debug.print("📤 Criando novo post...\n", .{});
    const new_post = try client.createPost(.{
        .userId = 1,
        .title = "Meu Post em Zig",
        .body = "Este post foi criado usando Zig e JSON!",
    });
    defer {
        allocator.free(new_post.title);
        allocator.free(new_post.body);
    }

    std.debug.print("✅ Post criado com ID: {d}\n", .{new_post.id});
    std.debug.print("   Título: {s}\n", .{new_post.title});
}

build.zig para o Cliente

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "api-client",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());

    const run_step = b.step("run", "Run API client");
    run_step.dependOn(&run_cmd.step);
}

Parte 5: Tratamento de Erros

5.1 Erros Comuns de Parsing

const std = @import("std");

const User = struct {
    id: u32,
    name: []const u8,
};

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

    // JSON inválido
    const invalid_json =
        \\{
        \\  "id": "not_a_number",
        \\  "name": "João"
        \\}
    ;

    // Tenta parsear e trata erro
    const result = std.json.parseFromSlice(
        User,
        allocator,
        invalid_json,
        .{},
    );

    if (result) |parsed| {
        defer parsed.deinit();
        std.debug.print("✅ Sucesso: {s}\n", .{parsed.value.name});
    } else |err| {
        std.debug.print("❌ Erro ao parsear JSON: {}\n", .{err});
        // Erro esperado: error.InvalidCharacter (id deveria ser número)
    }
}

5.2 Validação de Dados

const std = @import("std");

const CreateUserRequest = struct {
    email: []const u8,
    age: u32,

    pub fn validate(self: CreateUserRequest) !void {
        // Valida email
        if (std.mem.indexOf(u8, self.email, "@") == null) {
            return error.InvalidEmail;
        }

        // Valida idade
        if (self.age < 13 or self.age > 120) {
            return error.InvalidAge;
        }
    }
};

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

    const json_str =
        \\{
        \\  "email": "[email protected]",
        \\  "age": 25
        \\}
    ;

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

    // Valida após parse
    parsed.value.validate() catch |err| {
        std.debug.print("❌ Validação falhou: {}\n", .{err});
        return;
    };

    std.debug.print("✅ Dados válidos!\n", .{});
}

5.3 Estratégia de Fallback

const std = @import("std");

// Struct flexível que aceita variações
const ApiResponse = struct {
    status: []const u8,
    data: ?std.json.Value,        // Opcional
    error: ?[]const u8,           // Opcional
    message: ?[]const u8,         // Opcional (fallback)
};

pub fn handleApiResponse(allocator: std.mem.Allocator, json_str: []const u8) !void {
    const parsed = try std.json.parseFromSlice(
        ApiResponse,
        allocator,
        json_str,
        .{},
    );
    defer parsed.deinit();

    const response = parsed.value;

    if (std.mem.eql(u8, response.status, "error")) {
        // Tenta várias fontes de mensagem de erro
        const error_msg = response.error orelse
            (if (response.message) |m| m else "Erro desconhecido");
        std.debug.print("❌ Erro: {s}\n", .{error_msg});
        return;
    }

    std.debug.print("✅ Sucesso!\n", .{});

    if (response.data) |data| {
        // Processa dados
        std.debug.print("Dados recebidos: {}\n", .{data});
    }
}

Parte 6: Casos Avançados

6.1 JSON com Unions (Tipos Variados)

const std = @import("std");

// Union para representar valor que pode ser string ou número
const StringOrNumber = union(enum) {
    string: []const u8,
    number: f64,
};

const FlexibleValue = struct {
    id: u32,
    value: StringOrNumber,
};

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

    // JSON onde "value" pode ser string ou número
    const json_str =
        \\[
        \\  {"id": 1, "value": "texto"},
        \\  {"id": 2, "value": 42.5}
        \\]
    ;

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

    for (parsed.value) |item| {
        std.debug.print("ID: {d}, Value: ", .{item.id});
        switch (item.value) {
            .string => |s| std.debug.print("(string) {s}\n", .{s}),
            .number => |n| std.debug.print("(number) {d}\n", .{n}),
        }
    }
}

6.2 Parse Parcial de JSON

const std = @import("std");

// Parse apenas os campos que interessam
const PartialUser = struct {
    id: u32,
    name: []const u8,
    // Ignora: email, phone, website, company, address...
};

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

    // JSON grande com muitos campos
    const large_json =
        \\{
        \\  "id": 1,
        \\  "name": "João Silva",
        \\  "username": "joaosilva",
        \\  "email": "[email protected]",
        \\  "address": {
        \\    "street": "Rua Principal",
        \\    "city": "São Paulo"
        \\  },
        \\  "phone": "11-99999-9999",
        \\  "website": "joao.dev",
        \\  "company": {
        \\    "name": "Tech Corp",
        \\    "catchPhrase": "Inovação sempre"
        \\  }
        \\}
    ;

    // Parse só id e name — campos extras exigem ignore_unknown_fields.
    const parsed = try std.json.parseFromSlice(
        PartialUser,
        allocator,
        large_json,
        .{ .ignore_unknown_fields = true },
    );
    defer parsed.deinit();

    std.debug.print("ID: {d}, Nome: {s}\n", .{
        parsed.value.id,
        parsed.value.name,
    });
}

6.3 Ignorar Campos Desconhecidos

const std = @import("std");

const Config = struct {
    name: []const u8,
    version: []const u8,

    // Preferência moderna: passe a opção em parseFromSlice.
    // Alguns exemplos antigos usavam __json_options na struct;
    // no fluxo atual deste guia, a opção explícita no call site é suficiente.
};

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

    // JSON com campos extras que serão ignorados
    const json_str =
        \\{
        \\  "name": "meu-app",
        \\  "version": "1.0.0",
        \\  "unknown_field_1": "valor",
        \\  "unknown_field_2": 123,
        \\  "future_feature": true
        \\}
    ;

    // ignore_unknown_fields no call site — padrão recomendado no Zig 0.16.
    const parsed = try std.json.parseFromSlice(
        Config,
        allocator,
        json_str,
        .{ .ignore_unknown_fields = true },
    );
    defer parsed.deinit();

    std.debug.print("✅ Parse bem-sucedido ignorando campos extras\n", .{});
    std.debug.print("Name: {s}, Version: {s}\n", .{
        parsed.value.name,
        parsed.value.version,
    });
}

Resumo e Checklist

Você aprendeu a trabalhar com JSON em Zig:

✅ Parse básico — Structs ↔ JSON
✅ Arrays e campos opcionais — []T e ?T
✅ Serialização — Struct → JSON formatado
✅ JSON dinâmico — std.json.Value para estruturas desconhecidas
✅ Cliente de API — Requisições HTTP com JSON
✅ Tratamento de erros — Validação e fallback
✅ Casos avançados — Unions, parse parcial, campos ignorados

Tabela Rápida de Referência

OperaçãoFunçãoExemplo
Parse para structparseFromSlice(T, allocator, json, .{})const user = parsed.value;
Serializar structstringifyAlloc(allocator, value, .{})const json = try std.json.stringifyAlloc(...);
JSON dinâmicoparseFromSlice(std.json.Value, ...)root.object.get("key").?.string
Pretty print.{ .whitespace = .indent_2 }JSON com indentação
Campo opcional?T na structbio: ?[]const u8
Ignorar campos.ignore_unknown_fields = trueUsar em parseOptions
Campos duplicados.duplicate_field_behavior = .errorRejeita chave repetida
Exemplo curtostd.heap.page_allocatorOK em demos; evite em serviços
Liberar resultadodefer parsed.deinit()Sempre no mesmo escopo do valor

Perguntas Frequentes (FAQ)

Como faço para parsear JSON em Zig?

Use std.json.parseFromSlice(T, allocator, bytes, options). Leia o valor em parsed.value e chame parsed.deinit() quando terminar, pois o retorno pode possuir memória alocada pelo parser.

Por que preciso chamar deinit depois de parseFromSlice?

Porque parseFromSlice devolve um std.json.Parsed(T) que pode possuir strings, arrays e objetos alocados. parsed.deinit() libera essa memória; acessar parsed.value depois do deinit é inválido.

Posso usar page_allocator com parseFromSlice?

Sim em programas curtos e exemplos. Em serviços, loops ou testes de vazamento, prefira GeneralPurposeAllocator ou um arena com tempo de vida explícito, porque page_allocator não recupera pequenas alocações individualmente.

O que faz duplicate_field_behavior no parse JSON do Zig?

Controla chaves repetidas no mesmo objeto JSON. .error rejeita a entrada, .use_first conserva a primeira ocorrência e .use_last conserva a última. É independente de ignore_unknown_fields.

Como serializar uma struct para JSON em Zig?

Use std.json.stringify() / stringifyAlloc() para converter uma struct em string JSON, ou passe um writer para escrever diretamente.

Como lidar com campos opcionais no JSON em Zig?

Declare o campo como ?T = null para que um campo ausente use null. Para campos extras enviados pela API, passe .ignore_unknown_fields = true nas opções de parse.

Como parsear JSON de uma API HTTP em Zig?

Faça a requisição HTTP, leia o body da resposta e então use std.json.parseFromSlice() para converter o JSON em struct. Combine com o guia de cliente HTTP em Zig.

É possível trabalhar com JSON dinâmico em Zig?

Sim: use std.json.Value para representar JSON genérico/dinâmico e acessar campos por nome em tempo de execução, com menos type safety.

Como tratar erros de parsing JSON em Zig?

Use try/catch ou if/else para capturar erros. Erros comuns incluem error.SyntaxError, error.UnexpectedEndOfInput e error.InvalidCharacter. Sempre valide dados externos.

Próximos Passos

Aprofunde seus conhecimentos:

Exercícios Práticos

  1. Parse de API pública: Escreva um programa que consome a API do GitHub e lista repositórios de um usuário
  2. Configuração JSON: Crie um sistema de configuração que lê e escreve settings.json
  3. Validador de JSON: Implemente um programa que valida se um arquivo JSON está bem formado
  4. Conversor CSV→JSON: Converta arquivos CSV para formato JSON

O parsing de JSON em Zig é manual e explícito, sem reflection de runtime. Para comparar abordagens, Go usa encoding/json com struct tags para serialização automática, enquanto Python conta com o módulo json integrado na biblioteca padrão.

Recursos Adicionais


Gostou deste tutorial? Compartilhe com a comunidade Zig! Tem dúvidas ou sugestões? Entre em contato conosco.

Última atualização: 28 de setembro de 2026

API principal: std.json.parseFromSlice (Zig 0.16.0) — deinit, page_allocator e duplicate_field_behavior

Curso planejado · Zig na prática

Já programa? Construa a sua primeira CLI em Zig.

Conheça o curso planejado: fundamentos, erros, memória, testes e build no mesmo projeto. R$ 299 na pré-venda, pagamento único.

R$ 299Pagamento único na pré-venda Conhecer o curso

Continue aprendendo Zig

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