Bibliotecas JSON em Zig — Parsing, Serialização e Streaming
JSON é o formato de dados mais utilizado em APIs web e configurações modernas. O Zig oferece suporte robusto a JSON através da biblioteca padrão std.json, com capacidades de parsing tipado, parsing dinâmico e serialização. O ecossistema complementa com bibliotecas especializadas para streaming de grandes volumes e integrações com outros formatos.
std.json — A Biblioteca Padrão
Parsing Tipado (Recomendado)
A forma mais segura e performática de trabalhar com JSON em Zig é o parsing tipado, onde você define a estrutura esperada:
const std = @import("std");
const Usuario = struct {
id: u64,
nome: []const u8,
email: []const u8,
idade: ?u32 = null,
ativo: bool = true,
tags: []const []const u8 = &.{},
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
const json_str =
\\{
\\ "id": 42,
\\ "nome": "Maria Silva",
\\ "email": "maria@exemplo.com",
\\ "idade": 28,
\\ "ativo": true,
\\ "tags": ["admin", "dev"]
\\}
;
const parsed = try std.json.parseFromSlice(
Usuario,
allocator,
json_str,
.{},
);
defer parsed.deinit();
const usuario = parsed.value;
std.debug.print("Nome: {s}\n", .{usuario.nome});
std.debug.print("Email: {s}\n", .{usuario.email});
std.debug.print("Idade: {?}\n", .{usuario.idade});
}
Parsing Dinâmico
Quando a estrutura do JSON não é conhecida em tempo de compilação:
pub fn main() !void {
const allocator = std.heap.page_allocator;
const json_str =
\\{"chave": "valor", "numero": 42, "lista": [1, 2, 3]}
;
const parsed = try std.json.parseFromSlice(
std.json.Value,
allocator,
json_str,
.{},
);
defer parsed.deinit();
const root = parsed.value;
// Acessar campos dinamicamente
if (root.object.get("chave")) |valor| {
switch (valor) {
.string => |s| std.debug.print("chave = {s}\n", .{s}),
else => {},
}
}
if (root.object.get("numero")) |valor| {
switch (valor) {
.integer => |n| std.debug.print("numero = {}\n", .{n}),
else => {},
}
}
if (root.object.get("lista")) |valor| {
switch (valor) {
.array => |arr| {
for (arr.items) |item| {
switch (item) {
.integer => |n| std.debug.print("item = {}\n", .{n}),
else => {},
}
}
},
else => {},
}
}
}
Serialização (Zig para JSON)
const std = @import("std");
const Produto = struct {
id: u64,
nome: []const u8,
preco: f64,
disponivel: bool,
categorias: []const []const u8,
};
pub fn main() !void {
const produto = Produto{
.id = 1,
.nome = "Teclado Mecânico",
.preco = 349.90,
.disponivel = true,
.categorias = &.{ "periféricos", "teclados" },
};
// Serializar para string
var buf: [1024]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
try std.json.stringify(produto, .{}, stream.writer());
const json_str = stream.getWritten();
std.debug.print("{s}\n", .{json_str});
// Serializar com formatação (pretty print)
var buf2: [2048]u8 = undefined;
var stream2 = std.io.fixedBufferStream(&buf2);
try std.json.stringify(produto, .{ .whitespace = .indent_2 }, stream2.writer());
std.debug.print("{s}\n", .{stream2.getWritten()});
}
Opções de Parsing
const parsed = try std.json.parseFromSlice(MeuTipo, allocator, json_str, .{
.ignore_unknown_fields = true, // Ignorar campos desconhecidos
.allocate = .alloc_always, // Controle de alocação
.max_nest_depth = 64, // Profundidade máxima de aninhamento
});
Streaming de JSON Grande
Para arquivos JSON muito grandes que não cabem na memória:
const std = @import("std");
pub fn processarJsonGrande(reader: anytype) !void {
var scanner = std.json.Scanner(@TypeOf(reader)).init(reader);
while (true) {
const token = try scanner.next();
switch (token) {
.object_begin => std.debug.print("Início de objeto\n", .{}),
.object_end => std.debug.print("Fim de objeto\n", .{}),
.array_begin => std.debug.print("Início de array\n", .{}),
.array_end => std.debug.print("Fim de array\n", .{}),
.string => |s| std.debug.print("String: {s}\n", .{s}),
.number => |n| std.debug.print("Número: {s}\n", .{n}),
.true => std.debug.print("true\n", .{}),
.false => std.debug.print("false\n", .{}),
.null => std.debug.print("null\n", .{}),
.end_of_document => break,
else => {},
}
}
}
Padrões Comuns
API REST com JSON
const std = @import("std");
const ApiResponse = struct {
success: bool,
data: ?std.json.Value = null,
erro: ?[]const u8 = null,
timestamp: i64,
pub fn ok(data: anytype, allocator: std.mem.Allocator) ![]const u8 {
const response = ApiResponse{
.success = true,
.timestamp = std.time.timestamp(),
};
_ = data;
var buf = std.ArrayList(u8).init(allocator);
try std.json.stringify(response, .{}, buf.writer());
return buf.toOwnedSlice();
}
pub fn erro_resp(msg: []const u8, allocator: std.mem.Allocator) ![]const u8 {
const response = ApiResponse{
.success = false,
.erro = msg,
.timestamp = std.time.timestamp(),
};
var buf = std.ArrayList(u8).init(allocator);
try std.json.stringify(response, .{}, buf.writer());
return buf.toOwnedSlice();
}
};
Configuração de Aplicação
const Config = struct {
servidor: struct {
host: []const u8 = "0.0.0.0",
porta: u16 = 8080,
workers: u32 = 4,
} = .{},
banco: struct {
url: []const u8 = "postgresql://localhost/app",
pool_size: u32 = 10,
} = .{},
log: struct {
nivel: []const u8 = "info",
formato: []const u8 = "json",
} = .{},
};
pub fn carregarConfig(allocator: std.mem.Allocator) !Config {
const arquivo = try std.fs.cwd().openFile("config.json", .{});
defer arquivo.close();
const conteudo = try arquivo.readToEndAlloc(allocator, 1024 * 1024);
defer allocator.free(conteudo);
const parsed = try std.json.parseFromSlice(Config, allocator, conteudo, .{
.ignore_unknown_fields = true,
});
return parsed.value;
}
Performance
A implementação JSON do Zig é altamente performática:
| Operação | Zig std.json | Go encoding/json | Python json | Node.js JSON |
|---|---|---|---|---|
| Parse 1MB | ~3ms | ~8ms | ~25ms | ~5ms |
| Serialize 1MB | ~2ms | ~6ms | ~20ms | ~4ms |
| Memória | Mínima | Moderada | Alta | Moderada |
Boas Práticas
- Prefira parsing tipado: Mais seguro e performático que parsing dinâmico
- Use
ignore_unknown_fields: Permite evolução de APIs sem quebrar parsers - Trate erros de parsing: JSON inválido retorna erros descritivos
- Streaming para arquivos grandes: Evite carregar GBs de JSON na memória
- Valide após parsing: O parsing verifica formato, mas a lógica de negócio precisa validação adicional
Próximos Passos
Explore outras bibliotecas de serialização como MessagePack e Protocol Buffers, os frameworks web para APIs JSON, e os clientes HTTP para consumir APIs externas. Consulte nossos tutoriais para exemplos completos.