Trabalhando com JSON usando serde_json
1. Introdução ao serde_json e Configuração Inicial
O ecossistema Serde é o framework de serialização mais utilizado em Rust, oferecendo desempenho excepcional e segurança de tipos. O serde_json é o crate responsável por serializar e desserializar dados no formato JSON, aproveitando todo o poder do sistema de tipos do Rust.
Para começar, adicione as seguintes dependências ao seu Cargo.toml:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
A feature derive do serde permite usar macros como #[derive(Serialize, Deserialize)]. Para precisão arbitrária em números, adicione a feature arbitrary_precision ao serde_json:
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
2. Serialização de Dados Rust para JSON
Vamos começar serializando estruturas Rust para JSON. O serde_json oferece funções como to_string, to_vec e to_writer:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Usuario {
nome: String,
idade: u8,
email: String,
ativo: bool,
}
#[derive(Serialize, Deserialize, Debug)]
enum Status {
Ativo,
Inativo,
Pendente,
}
fn main() {
let usuario = Usuario {
nome: "Alice".to_string(),
idade: 30,
email: "alice@exemplo.com".to_string(),
ativo: true,
};
// Serialização padrão (compacta)
let json = serde_json::to_string(&usuario).unwrap();
println!("JSON compacto: {}", json);
// Serialização formatada (pretty)
let json_pretty = serde_json::to_string_pretty(&usuario).unwrap();
println!("JSON formatado:\n{}", json_pretty);
// Serializando para bytes
let bytes = serde_json::to_vec(&usuario).unwrap();
println!("Bytes: {:?}", bytes);
// Serializando coleções
let numeros = vec![1, 2, 3, 4, 5];
println!("Vetor: {}", serde_json::to_string(&numeros).unwrap());
// Serializando opcionais
let opcional: Option<String> = Some("valor".to_string());
println!("Optional: {}", serde_json::to_string(&opcional).unwrap());
}
3. Desserialização de JSON para Tipos Rust
A desserialização converte JSON de volta para tipos Rust. As funções principais são from_str, from_reader e from_slice:
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::BufReader;
#[derive(Deserialize, Debug)]
struct Produto {
id: u32,
nome: String,
preco: f64,
#[serde(default)]
descricao: Option<String>,
}
fn main() -> Result<(), serde_json::Error> {
// Desserializando de string
let json_str = r#"
{
"id": 1,
"nome": "Notebook",
"preco": 2500.50,
"descricao": "Notebook de última geração"
}"#;
let produto: Produto = serde_json::from_str(json_str)?;
println!("Produto: {:?}", produto);
// Desserializando de arquivo
let arquivo = File::open("produto.json").unwrap_or_else(|_| {
eprintln!("Arquivo não encontrado, criando exemplo...");
let json = r#"{"id":2,"nome":"Mouse","preco":89.90}"#;
std::fs::write("produto.json", json).unwrap();
File::open("produto.json").unwrap()
});
let reader = BufReader::new(arquivo);
let produto_arquivo: Produto = serde_json::from_reader(reader)?;
println!("Produto do arquivo: {:?}", produto_arquivo);
// Tratamento de erros
let json_invalido = r#"{"id": "nao_eh_numero"}"#;
match serde_json::from_str::<Produto>(json_invalido) {
Ok(p) => println!("{}", p.id),
Err(e) => eprintln!("Erro ao desserializar: {}", e),
}
Ok(())
}
4. Customização com Atributos Serde
Os atributos Serde permitem controle fino sobre como os dados são serializados e desserializados:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Configuracao {
#[serde(rename = "db_host")]
database_host: String,
#[serde(rename = "db_port")]
database_port: u16,
#[serde(skip_serializing_if = "Option::is_none")]
timeout: Option<u64>,
#[serde(default = "default_max_conexoes")]
max_conexoes: u32,
}
fn default_max_conexoes() -> u32 {
10
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "tipo", content = "dados")]
enum Mensagem {
Texto(String),
Imagem { url: String, largura: u32, altura: u32 },
Audio { duracao: f64 },
}
fn main() {
let config = Configuracao {
database_host: "localhost".to_string(),
database_port: 5432,
timeout: Some(30),
max_conexoes: 5,
};
let json = serde_json::to_string_pretty(&config).unwrap();
println!("Configuração:\n{}", json);
// Exemplo com flatten para achatar structs aninhadas
#[derive(Serialize, Deserialize)]
struct Endereco {
rua: String,
cidade: String,
}
#[derive(Serialize, Deserialize)]
struct Pessoa {
nome: String,
#[serde(flatten)]
endereco: Endereco,
}
let pessoa = Pessoa {
nome: "João".to_string(),
endereco: Endereco {
rua: "Rua A".to_string(),
cidade: "São Paulo".to_string(),
},
};
println!("Pessoa com flatten: {}", serde_json::to_string(&pessoa).unwrap());
// Enum com tag
let msg = Mensagem::Texto("Olá".to_string());
println!("Mensagem: {}", serde_json::to_string(&msg).unwrap());
}
5. Trabalhando com JSON Dinâmico e Não Tipado
Para situações onde a estrutura do JSON é desconhecida, use serde_json::Value:
use serde_json::{Value, json};
fn main() {
// Parsing de JSON dinâmico
let dados = r#"
{
"nome": "Maria",
"idade": 28,
"habilidades": ["Rust", "Python", "JavaScript"],
"endereco": {
"cidade": "Rio de Janeiro",
"pais": "Brasil"
}
}"#;
let valor: Value = serde_json::from_str(dados).unwrap();
// Acessando dados
if let Some(nome) = valor.get("nome") {
println!("Nome: {}", nome);
}
if let Some(idade) = valor.get("idade").and_then(|v| v.as_u64()) {
println!("Idade: {}", idade);
}
// Navegação em estruturas aninhadas
if let Some(cidade) = valor["endereco"]["cidade"].as_str() {
println!("Cidade: {}", cidade);
}
// Iterando sobre arrays
if let Some(habilidades) = valor["habilidades"].as_array() {
for (i, habilidade) in habilidades.iter().enumerate() {
println!("Habilidade {}: {}", i + 1, habilidade);
}
}
// Construção programática com macro json!
let novo_json = json!({
"status": "sucesso",
"dados": {
"usuarios": [
{"nome": "Ana", "idade": 25},
{"nome": "Pedro", "idade": 32}
],
"total": 2
},
"metadados": null
});
println!("JSON construído: {}", serde_json::to_string_pretty(&novo_json).unwrap());
// Modificando valores
if let Some(obj) = novo_json.as_object() {
println!("Chaves disponíveis: {:?}", obj.keys());
}
}
6. Performance e Boas Práticas
Para aplicações que exigem alta performance, considere estas técnicas:
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use std::io::{BufWriter, Write};
#[derive(Deserialize)]
struct DadoStream {
id: u32,
#[serde(borrow)]
conteudo: &' RawValue, // Evita alocação desnecessária
}
fn processar_grande_arquivo() -> Result<(), Box<dyn std::error::Error>> {
// Leitura streaming para arquivos grandes
let dados = r#"{"id":1,"conteudo":"dado1"}
{"id":2,"conteudo":"dado2"}
{"id":3,"conteudo":"dado3"}"#;
let stream = serde_json::Deserializer::from_str(dados)
.into_iter::<DadoStream>();
for resultado in stream {
match resultado {
Ok(item) => println!("Processando item {}", item.id),
Err(e) => eprintln!("Erro no streaming: {}", e),
}
}
// Escrita direta em buffer para evitar alocações
let mut buffer = Vec::new();
let mut writer = BufWriter::new(&mut buffer);
let dados = vec![
json!({"nome": "Item1", "valor": 100}),
json!({"nome": "Item2", "valor": 200}),
];
for item in &dados {
serde_json::to_writer(&mut writer, item)?;
writeln!(&mut writer)?; // Adiciona nova linha entre objetos
}
writer.flush()?;
println!("Buffer final: {:?}", String::from_utf8_lossy(&buffer));
Ok(())
}
7. Casos Especiais e Interoperabilidade
use serde::{Deserialize, Serialize};
use serde_json::Number;
use std::str::FromStr;
// Números de precisão arbitrária
#[derive(Serialize, Deserialize, Debug)]
struct Transacao {
#[serde(with = "serde_json::number")]
valor_exato: Number,
descricao: String,
}
// Custom serialização para datas
#[derive(Debug)]
struct DataCustomizada {
timestamp: i64,
}
impl Serialize for DataCustomizada {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("DataCustomizada", 1)?;
state.serialize_field("timestamp", &self.timestamp)?;
state.end()
}
}
impl<'de> Deserialize<'de> for DataCustomizada {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let valor = serde_json::Value::deserialize(deserializer)?;
let timestamp = valor["timestamp"]
.as_i64()
.ok_or_else(|| serde::de::Error::custom("timestamp inválido"))?;
Ok(DataCustomizada { timestamp })
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Precisão arbitrária
let json_str = r#"{"valor_exato": 123456789012345678901234567890, "descricao": "teste"}"#;
let transacao: Transacao = serde_json::from_str(json_str)?;
println!("Transação: {:?}", transacao);
// Re-serializando mantém precisão
let json_saida = serde_json::to_string(&transacao)?;
println!("JSON de saída: {}", json_saida);
// Data customizada
let data = DataCustomizada { timestamp: 1700000000 };
let json_data = serde_json::to_string(&data)?;
println!("Data serializada: {}", json_data);
let data_desserializada: DataCustomizada = serde_json::from_str(&json_data)?;
println!("Data desserializada: {:?}", data_desserializada);
// Integração com logging
let evento = json!({
"nivel": "info",
"mensagem": "Operação concluída",
"dados": {
"usuario": "admin",
"acao": "login"
}
});
println!("Evento para logging: {}", evento);
Ok(())
}
Referências
- Documentação oficial do serde_json — Documentação completa com todos os tipos, funções e exemplos de uso do crate
- Guia Serde: Serialização e Desserialização — Tutorial oficial do ecossistema Serde com exemplos práticos e melhores práticas
- Rust Cookbook: JSON — Receitas práticas para trabalhar com JSON em Rust, incluindo manipulação de arquivos
- Serde Attributes Reference — Referência completa de todos os atributos Serde para customização de serialização
- Performance Guide: serde_json — Guia de performance no repositório oficial com benchmarks e técnicas de otimização
- JSON Streaming em Rust — Documentação do StreamDeserializer para processamento eficiente de grandes arquivos JSON