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.
Resposta rápida (Zig 0.16.0): use std.json.Stringify.valueAlloc(allocator, valor, .{}) para converter uma struct (ou std.json.Value) em []u8. O caller é dono da memória e deve chamar allocator.free. Para escrever direto em um writer, use std.json.Stringify.value. Essa API substitui o antigo std.json.stringify / stringifyAlloc.
const json = try std.json.Stringify.valueAlloc(allocator, usuario, .{});
defer allocator.free(json);
Nesta receita você serializa structs, arrays, campos opcionais e JSON dinâmico, e vê quando cair para Writer.Allocating / ArrayList managed.
Pré-requisitos
- Zig 0.16.0, versão usada para compilar e executar os exemplos completos desta receita. Confira com
zig version; veja o guia de instalação. - Conhecimento de parsing JSON em Zig.
- Noção básica de allocators. Aqui usamos
std.heap.DebugAllocator:GeneralPurposeAllocatornão existe no Zig 0.16.0.
API moderna: Stringify.value e valueAlloc
| Função | Quando usar | Retorno |
|---|---|---|
std.json.Stringify.valueAlloc(gpa, v, opts) | Respostas curtas, testes, buffers owned | ![]u8 (caller frees) |
std.json.Stringify.value(v, opts, writer) | Streaming para arquivo, socket ou Writer.Allocating | !void |
std.json.fmt(v, opts) | Interpolar JSON em print / formatters | formatter |
O antigo std.json.stringifyAlloc não é o caminho documentado no 0.16 — se um snippet antigo falhar no compile, troque para Stringify.valueAlloc.
Serializar uma Struct com valueAlloc
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.DebugAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const usuario = Usuario{
.nome = "Maria Silva",
.email = "[email protected]",
.idade = 28,
.ativo = true,
};
// std.json.Stringify.valueAlloc — substituto de stringifyAlloc
const json = try std.json.Stringify.valueAlloc(allocator, usuario, .{});
defer allocator.free(json);
std.debug.print("{s}\n", .{json});
}
Saída esperada
{"nome":"Maria Silva","email":"[email protected]","idade":28,"ativo":true}
JSON Formatado (Pretty Print)
Para JSON legível com indentação, passe Stringify.Options:
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.DebugAllocator(.{}){};
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,
};
const json = try std.json.Stringify.valueAlloc(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
}
value + Writer.Allocating (e ArrayList managed)
Quando você já controla o buffer — ou quer reutilizar capacidade entre serializações — use Stringify.value com std.Io.Writer.Allocating. Esse é o mesmo motor interno de valueAlloc, e é o ponto em que a documentação da std cita ArrayList / array_list.Managed:
const std = @import("std");
const Evento = struct {
tipo: []const u8,
timestamp: i64,
dados: []const u8,
};
pub fn main() !void {
var gpa = std.heap.DebugAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const evento = Evento{
.tipo = "login",
.timestamp = 1740100000,
.dados = "usuario=maria",
};
// Caminho 1: Writer.Allocating direto (equivalente prático a valueAlloc)
var aw: std.Io.Writer.Allocating = .init(allocator);
defer aw.deinit();
try std.json.Stringify.value(evento, .{}, &aw.writer);
const json1 = try aw.toOwnedSlice();
defer allocator.free(json1);
std.debug.print("Allocating: {s}\n", .{json1});
// Caminho 2: buffer ArrayList(u8) + fromArrayList
// Útil quando o ArrayList já existe no seu pipeline (HTTP body, fila, etc.)
var list: std.ArrayList(u8) = .empty;
defer list.deinit(allocator);
var aw_list = std.Io.Writer.Allocating.fromArrayList(allocator, &list);
defer aw_list.deinit(); // libera o buffer mesmo se a serialização falhar
try std.json.Stringify.value(evento, .{}, &aw_list.writer);
// devolve o ArrayList atualizado; items é o JSON
list = aw_list.toArrayList();
std.debug.print("ArrayList: {s}\n", .{list.items});
}
Notas rápidas:
- No Zig 0.16.0,
std.ArrayList(T)é unmanaged: o allocator vai nos métodos. Jástd.json.Arrayéstd.array_list.Managed(std.json.Value)e guarda o allocator internamente. Um buffer de bytes e um array de valores JSON são tipos distintos. fromArrayListtransfere a memória delistpara o writer e deixa a lista vazia.toArrayListtransfere de volta e esvazia o writer. Odefer aw_list.deinit()é necessário no caminho de erro; depois da transferência de volta ele é seguro porque o writer está vazio.- Prefira
valueAllocquando só precisa do[]u8final. PrefirafromArrayListquando o buffer faz parte de um fluxo maior. A serialização acrescenta bytes ao buffer existente; limpe o conteúdo antes se quiser substituir o JSON anterior.
Serializar Arrays e Slices
Arrays e slices viram arrays JSON automaticamente:
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.DebugAllocator(.{}){};
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.Stringify.valueAlloc(allocator, &produtos, .{
.whitespace = .indent_2,
});
defer allocator.free(json);
std.debug.print("{s}\n", .{json});
}
Campos Opcionais (null em JSON)
Campos ?T saem como null quando vazios (comportamento padrão de emit_null_optional_fields):
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.DebugAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const perfil_completo = Perfil{
.nome = "João Dev",
.bio = "Desenvolvedor Zig apaixonado",
.site = "https://joaodev.com",
.seguidores = 1500,
};
const json1 = try std.json.Stringify.valueAlloc(allocator, perfil_completo, .{
.whitespace = .indent_2,
});
defer allocator.free(json1);
std.debug.print("Completo:\n{s}\n\n", .{json1});
const perfil_minimo = Perfil{
.nome = "Ana",
.bio = null,
.site = null,
.seguidores = null,
};
const json2 = try std.json.Stringify.valueAlloc(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
}
Para omitir chaves nulas em vez de emitir null, passe .{ .emit_null_optional_fields = false }.
Construir JSON Dinamicamente (std.json.Value)
Para estrutura dinâmica, monte um std.json.Value e serialize com valueAlloc. Por baixo, std.json.Array usa array_list.Managed(Value):
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.DebugAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var root = std.json.Value{ .object = .{} };
defer root.object.deinit(allocator);
try root.object.put(allocator, "versao", .{ .string = "1.0.0" });
try root.object.put(allocator, "nome", .{ .string = "meu-app" });
try root.object.put(allocator, "porta", .{ .integer = 3000 });
var deps: std.json.ObjectMap = .{};
defer deps.deinit(allocator);
try deps.put(allocator, "std", .{ .string = "0.16.0" });
try root.object.put(allocator, "dependencias", .{ .object = deps });
// std.json.Array é uma lista managed de Value, não um buffer de bytes.
var linguagens = std.json.Array.init(allocator);
defer linguagens.deinit();
try linguagens.append(.{ .string = "Zig" });
try linguagens.append(.{ .string = "C" });
try root.object.put(allocator, "linguagens", .{ .array = linguagens });
const json = try std.json.Stringify.valueAlloc(allocator, root, .{
.whitespace = .indent_2,
});
defer allocator.free(json);
std.debug.print("{s}\n", .{json});
}
O objeto gerado inclui "linguagens": ["Zig", "C"]. No Zig 0.16.0, ObjectMap recebe o allocator em put e deinit, enquanto std.json.Array.init(allocator) o guarda internamente. root.object.deinit(allocator) libera apenas o armazenamento do mapa, não percorre recursivamente os objetos e arrays filhos. Por isso deps e linguagens têm seus próprios defer. As cópias inseridas em root são usadas apenas enquanto esses containers estão vivos; não libere a cópia e o original duas vezes. Aqui as strings são literais; strings alocadas separadamente exigem sua própria liberação. O []u8 retornado por valueAlloc é outra alocação independente.
Como verificar os exemplos e corrigir erros comuns
Salve qualquer exemplo completo acima como gerar-json.zig e execute:
zig version
zig run gerar-json.zig
A versão usada nesta receita é 0.16.0. std.debug.print escreve em stderr: para capturar o JSON dos exemplos que imprimem apenas JSON, use zig run gerar-json.zig 2> saida.json.
| Sintoma | Correção no Zig 0.16.0 |
|---|---|
std.heap não tem GeneralPurposeAllocator | Use std.heap.DebugAllocator(.{}){} e seu método allocator() |
std.json não tem stringifyAlloc | Use std.json.Stringify.valueAlloc(allocator, valor, .{}) |
Stringify.value rejeita o writer | Passe &aw.writer de um std.Io.Writer.Allocating, não o writer antigo |
Leak ao falhar em fromArrayList | Adicione defer aw_list.deinit() antes de serializar |
| Array JSON tratado como texto | Use std.json.Array para valores; []const u8 serializa como string JSON |
Migração rápida: stringifyAlloc → valueAlloc
| Antes | Agora (Zig 0.16) |
|---|---|
std.json.stringifyAlloc(a, v, opts) | std.json.Stringify.valueAlloc(a, v, opts) |
std.json.stringify(v, opts, writer) | std.json.Stringify.value(v, opts, writer) |
writer “anytype” antigo (std.io.getStdOut().writer()) | *std.Io.Writer (API de I/O nova) |
Se você só atualizou o nome da função e o compile ainda reclama do writer, migre o I/O junto — value espera o writer moderno, não o anytype antigo.
Dicas e Boas Práticas
- Structs tipadas primeiro: quando o schema é conhecido, serialize a struct;
Valueé para JSON aberto. valueAllocpara o happy path curto;value+ writer para streaming e buffers reutilizados.- Pretty print só em config/debug: rede e APIs ficam melhor com o default minificado.
- Campos opcionais:
?T+emit_null_optional_fieldscontrolamnullvs omissão. - Combine com HTTP: gere o body e envie via HTTP POST.
- Parse do outro lado: veja Parsing JSON em Zig (
parseFromSlice+deinit).
Perguntas Frequentes (FAQ)
Como gerar JSON em Zig 0.16?
Use std.json.Stringify.valueAlloc(allocator, valor, options) para obter uma []u8 JSON. O caller libera com allocator.free. Para streaming, use std.json.Stringify.value com um *std.Io.Writer.
O que é std.json.Stringify.valueAlloc?
É a API alocada de serialização JSON do Zig atual. Substitui o antigo std.json.stringifyAlloc: recebe o allocator, o valor e Stringify.Options, e devolve a string JSON em memória dinâmica.
stringifyAlloc ainda existe no Zig 0.16?
Não no caminho moderno. O módulo passou a expor std.json.Stringify com value (writer) e valueAlloc (allocator). Código antigo com stringify/stringifyAlloc precisa migrar para essas funções.
Como valueAlloc se relaciona com ArrayList managed?
Por baixo, valueAlloc usa std.Io.Writer.Allocating. Se você já tem um ArrayList(u8), pode ligar o writer com Writer.Allocating.fromArrayList e chamar Stringify.value — o padrão array_list managed aparece nos exemplos da std quando o buffer precisa crescer.
Qual a diferença entre Stringify.value e valueAlloc?
value escreve em um *std.Io.Writer (arquivo, socket, buffer). valueAlloc cria um Writer.Allocating interno, serializa e devolve []u8 owned. Prefira value em hot paths; valueAlloc em respostas curtas e testes.
Receitas Relacionadas
- Como parsear JSON em Zig — Operação inversa
- Como fazer requisições HTTP POST em Zig — Enviar JSON via HTTP
- Arrays Dinâmicos com ArrayList — Buffer dinâmico por trás do writer
Referências oficiais (Zig 0.16.0)
- Código de
std.json.Stringify, incluindovalueAlloce suas opções. - Definições de
std.json.Value,ArrayeObjectMap. - Contrato de transferência de memória de
Writer.Allocating.
Essas referências estão fixadas na versão dos exemplos, em vez de acompanhar mudanças da branch de desenvolvimento.
Tutoriais Relacionados
- Parsing JSON em Zig —
parseFromSlice,deinite opções no 0.16 - Referência std.json Stringify — Tabela de opções e tipos