Introdução
Ler e salvar JSON em arquivos é essencial para persistência de dados, arquivos de configuração, cache local e exportação de dados. Em Zig, combinamos std.fs para I/O de arquivos com std.json para serialização e deserialização.
Nesta receita, você aprenderá a ler JSON de arquivos, salvar dados como JSON e manipular arquivos de configuração.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento de parsing JSON e geração de JSON
Ler JSON de um Arquivo
Leia e parseie um arquivo JSON completo:
const std = @import("std");
const fs = std.fs;
const Config = struct {
host: []const u8,
porta: u16,
debug: bool,
max_conexoes: u32,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Ler o arquivo inteiro
const data = fs.cwd().readFileAlloc(
allocator,
"config.json",
1024 * 1024, // máximo 1 MB
) catch |err| {
std.debug.print("Erro ao ler arquivo: {}\n", .{err});
return;
};
defer allocator.free(data);
// Parsear o JSON
const parsed = try std.json.parseFromSlice(Config, allocator, data, .{
.ignore_unknown_fields = true,
});
defer parsed.deinit();
const config = parsed.value;
std.debug.print("Configuração carregada:\n", .{});
std.debug.print(" Host: {s}\n", .{config.host});
std.debug.print(" Porta: {d}\n", .{config.porta});
std.debug.print(" Debug: {}\n", .{config.debug});
std.debug.print(" Max conexões: {d}\n", .{config.max_conexoes});
}
Salvar JSON em um Arquivo
Serialize uma struct e salve em disco:
const std = @import("std");
const fs = std.fs;
const AppState = struct {
versao: []const u8,
ultimo_acesso: i64,
usuarios_ativos: u32,
configuracoes: struct {
tema: []const u8,
idioma: []const u8,
notificacoes: bool,
},
};
fn saveJson(comptime T: type, data: T, path: []const u8) !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Serializar para JSON com formatação
const json = try std.json.stringifyAlloc(allocator, data, .{
.whitespace = .indent_2,
});
defer allocator.free(json);
// Escrever no arquivo
const file = try fs.cwd().createFile(path, .{});
defer file.close();
try file.writeAll(json);
std.debug.print("Salvo em '{s}' ({d} bytes)\n", .{ path, json.len });
}
pub fn main() !void {
const state = AppState{
.versao = "2.1.0",
.ultimo_acesso = 1740100000,
.usuarios_ativos = 42,
.configuracoes = .{
.tema = "escuro",
.idioma = "pt-BR",
.notificacoes = true,
},
};
try saveJson(AppState, state, "estado.json");
}
Arquivo gerado (estado.json)
{
"versao": "2.1.0",
"ultimo_acesso": 1740100000,
"usuarios_ativos": 42,
"configuracoes": {
"tema": "escuro",
"idioma": "pt-BR",
"notificacoes": true
}
}
Gerenciador de Configuração Completo
Uma classe que carrega, modifica e salva configurações JSON:
const std = @import("std");
const fs = std.fs;
const Config = struct {
servidor: struct {
host: []const u8 = "localhost",
porta: u16 = 8080,
} = .{},
log: struct {
nivel: []const u8 = "info",
arquivo: []const u8 = "app.log",
} = .{},
max_threads: u32 = 4,
};
const ConfigManager = struct {
config: Config,
path: []const u8,
allocator: std.mem.Allocator,
_parsed: ?std.json.Parsed(Config),
pub fn init(allocator: std.mem.Allocator, path: []const u8) ConfigManager {
return .{
.config = .{},
.path = path,
.allocator = allocator,
._parsed = null,
};
}
pub fn load(self: *ConfigManager) !void {
const data = fs.cwd().readFileAlloc(
self.allocator,
self.path,
1024 * 1024,
) catch |err| {
if (err == error.FileNotFound) {
std.debug.print("Arquivo não encontrado. Usando padrões.\n", .{});
self.config = .{};
return;
}
return err;
};
defer self.allocator.free(data);
if (self._parsed) |*p| p.deinit();
self._parsed = try std.json.parseFromSlice(Config, self.allocator, data, .{
.ignore_unknown_fields = true,
});
self.config = self._parsed.?.value;
}
pub fn save(self: *ConfigManager) !void {
const json = try std.json.stringifyAlloc(self.allocator, self.config, .{
.whitespace = .indent_2,
});
defer self.allocator.free(json);
const file = try fs.cwd().createFile(self.path, .{});
defer file.close();
try file.writeAll(json);
}
pub fn deinit(self: *ConfigManager) void {
if (self._parsed) |*p| p.deinit();
}
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var mgr = ConfigManager.init(allocator, "app_config.json");
defer mgr.deinit();
// Tentar carregar configuração existente
try mgr.load();
std.debug.print("Configuração atual:\n", .{});
std.debug.print(" Host: {s}\n", .{mgr.config.servidor.host});
std.debug.print(" Porta: {d}\n", .{mgr.config.servidor.porta});
std.debug.print(" Log: {s}\n", .{mgr.config.log.nivel});
std.debug.print(" Threads: {d}\n", .{mgr.config.max_threads});
// Salvar a configuração
try mgr.save();
std.debug.print("\nConfiguração salva com sucesso!\n", .{});
}
Atualizar JSON Existente
Leia, modifique e salve novamente:
const std = @import("std");
const fs = std.fs;
fn updateJsonField(
allocator: std.mem.Allocator,
path: []const u8,
field: []const u8,
new_value: std.json.Value,
) !void {
// Ler arquivo existente
const data = try fs.cwd().readFileAlloc(allocator, path, 1024 * 1024);
defer allocator.free(data);
// Parsear como valor dinâmico
var parsed = try std.json.parseFromSlice(std.json.Value, allocator, data, .{});
defer parsed.deinit();
// Modificar o campo
if (parsed.value == .object) {
try parsed.value.object.put(field, new_value);
}
// Serializar de volta
const json = try std.json.stringifyAlloc(allocator, parsed.value, .{
.whitespace = .indent_2,
});
defer allocator.free(json);
// Salvar
const file = try fs.cwd().createFile(path, .{});
defer file.close();
try file.writeAll(json);
std.debug.print("Campo '{s}' atualizado com sucesso.\n", .{field});
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Primeiro, criar o arquivo
const initial = "{\"versao\": \"1.0.0\", \"porta\": 3000}";
{
const file = try fs.cwd().createFile("dados.json", .{});
defer file.close();
try file.writeAll(initial);
}
// Atualizar um campo
try updateJsonField(
allocator,
"dados.json",
"porta",
.{ .integer = 8080 },
);
}
Dicas e Boas Práticas
Limite o tamanho: Sempre defina um limite máximo ao ler arquivos para evitar uso excessivo de memória.
Trate FileNotFound: Para configurações, use valores padrão quando o arquivo não existir.
Use formatação para configs: Para arquivos editados manualmente, use indentação (
.whitespace = .indent_2).Atomicidade: Para dados críticos, escreva em um arquivo temporário e renomeie para evitar corrupção.
Gerencie memória: Use o gerenciamento de memória do Zig cuidadosamente com parsed values.
Receitas Relacionadas
- Como parsear JSON em Zig - Fundamentos do parsing
- Como gerar JSON em Zig - Serialização detalhada
- Como validar JSON em Zig - Validar antes de salvar
- Como parsear CSV em Zig - Formato alternativo