Serialização com serde
Esta aula explora a serialização e desserialização em Rust usando a biblioteca serde, cobrindo os traits Serialize e Deserialize, o uso de serde_json, atributos de personalização e diferentes formatos de serialização, com exemplos práticos e exercícios.
Bem-vindo à aula sobre serialização com serde! Nesta aula, vamos mergulhar em uma das bibliotecas mais importantes do ecossistema Rust: serde. A serialização é o processo de converter estruturas de dados em um formato que possa ser armazenado ou transmitido (como JSON, YAML, ou binário), e a desserialização é o processo inverso. Serde (abreviação de Serialization/Deserialization) é um framework que fornece uma maneira eficiente e genérica de fazer isso, com suporte a muitos formatos e personalizações.
Vamos aprender a usar os traits Serialize e Deserialize, trabalhar com o formato mais comum (JSON), explorar atributos para controlar a serialização e conhecer outros formatos suportados. Ao final, você terá uma base sólida para serializar dados em seus projetos Rust.
Serialize e Deserialize
Os pilares do serde são os traits Serialize e Deserialize. Quando você deriva esses traits em suas structs e enums, elas se tornam capazes de se converter em um formato serializado e de serem reconstruídas a partir dele. A derivação é feita com o atributo #[derive(Serialize, Deserialize)].
Esses traits são genéricos sobre o formato, o que significa que a mesma struct pode ser serializada em JSON, YAML, ou qualquer outro formato suportado sem alterar a definição. O serde cuida de mapear os campos da struct para os nomes e tipos correspondentes no formato alvo.
Vamos ver um exemplo básico:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Usuario {
nome: String,
idade: u32,
email: String,
}
fn main() {
let usuario = Usuario {
nome: "Ana".to_string(),
idade: 30,
email: "ana@example.com".to_string(),
};
// Aqui podemos serializar para vários formatos
}O trait Serialize é implementado automaticamente pela derivação, e o mesmo para Deserialize. Você não precisa escrever implementações manuais na maioria dos casos. Para tipos mais complexos, como enums com dados, a derivação também funciona bem.
serde_json
O crate serde_json é a implementação mais popular do formato JSON para serde. Ele fornece funções como to_string, to_vec, from_str e from_slice para serializar e desserializar dados. Para usar, adicione as dependências no Cargo.toml:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"Exemplo prático de serialização para JSON:
use serde::{Serialize, Deserialize};
use serde_json;
#[derive(Serialize, Deserialize)]
struct Produto {
id: u32,
nome: String,
preco: f64,
}
fn main() {
let produto = Produto {
id: 1,
nome: "Teclado".to_string(),
preco: 99.90,
};
// Serializa para uma string JSON
let json = serde_json::to_string(&produto).unwrap();
println!("{}", json); // {"id":1,"nome":"Teclado","preco":99.9}
// Desserializa de volta para a struct
let produto2: Produto = serde_json::from_str(&json).unwrap();
println!("{:?}", produto2);
}Além de structs, você pode serializar coleções, tuplas e até valores genéricos. O serde_json também oferece um tipo Value que representa qualquer JSON, útil para processamento dinâmico.
Atributos
O serde fornece uma série de atributos para personalizar a serialização e desserialização. Eles são colocados antes dos campos ou na própria struct/enum. Alguns dos mais úteis incluem:
#[serde(rename = "nome_alternativo")]: muda o nome do campo no formato serializado.#[serde(rename_all = "camelCase")]: aplica uma regra de nomenclatura a todos os campos (ex.: snake_case para camelCase).#[serde(default)]: usa o valor padrão do tipo se o campo estiver ausente na desserialização.#[serde(skip_serializing_if = "Option::is_none")]: omite o campo se a condição for verdadeira.#[serde(flatten)]: achata os campos de uma struct aninhada.
Exemplo com atributos:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Config {
host: String,
port: u16,
#[serde(default = "default_timeout")]
timeout: u64,
}
fn default_timeout() -> u64 { 30 }
fn main() {
let json = r#"{"host":"localhost","port":8080}"#;
let config: Config = serde_json::from_str(json).unwrap();
println!("{}:{}", config.host, config.port); // timeout usa default 30
}Esses atributos permitem adaptar suas estruturas de dados a diferentes formatos e requisitos sem alterar a lógica principal.
Formatos
Embora JSON seja o formato mais comum, o serde suporta muitos outros via crates adicionais. Alguns exemplos:
- JSON (
serde_json): formato textual leve. - YAML (
serde_yaml): formato legível por humanos, com indentação. - TOML (
toml): usado em configurações, como Cargo.toml. - MessagePack (
rmp-serde): formato binário compacto. - BSON (
bson): usado com MongoDB. - Postcard (
postcard): binário muito compacto para sistemas embarcados.
Cada formato tem suas vantagens: JSON é amplamente usado em APIs web, YAML é mais legível para configurações, e formatos binários são mais rápidos e compactos. A beleza do serde é que você pode trocar de formato simplesmente mudando a função chamada, mantendo o mesmo código de dados.
Exemplo com YAML (adicione serde_yaml ao Cargo.toml):
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Pessoa {
nome: String,
idade: u8,
}
fn main() {
let pessoa = Pessoa { nome: "Carlos".to_string(), idade: 25 };
let yaml = serde_yaml::to_string(&pessoa).unwrap();
println!("{}", yaml);
let de: Pessoa = serde_yaml::from_str(&yaml).unwrap();
}
Boas Práticas e Observações
Ao trabalhar com serde, considere as seguintes boas práticas:
- Use
#[serde(default)]para campos que podem ser omitidos, evitando erros de desserialização. - Prefira tipos como
Optionpara campos opcionais, permitindo que o serde trate a ausência corretamente. - Para enums, use
#[serde(tag = "tipo")]para serialização com tag, facilitando a compatibilidade com formatos como JSON. - Teste a serialização e desserialização em casos de borda, como strings vazias ou números negativos.
- Mantenha o formato de serialização estável para evitar quebras de compatibilidade em APIs.
Referências
- Site oficial do serde
- Documentação do crate serde
- Documentação do serde_json
- Documentação do serde_yaml
- Documentação do crate toml
- Repositório do serde no GitHub
Exercícios
- Exercício 1: Crie uma struct
Pontocom camposxey(f64) e serialize para JSON usando serde_json. Imprima o JSON resultante. - Exercício 2: Usando a struct do exercício 1, desserialize a string JSON
{"x":3.5,"y":4.5}em um valorPontoe imprima os campos. - Exercício 3: Adicione um atributo
#[serde(rename_all = "camelCase")]a uma struct com camposnome_completoeidade_usuario, serialize e observe a saída JSON. - Exercício 4: Crie uma enum
Statuscom variantesAtivo,InativoePendente, e serialize para JSON. Use#[serde(rename_all = "lowercase")]para que os valores sejam minúsculos. - Exercício 5: Use o atributo
#[serde(default)]em um campo do tipou32e desserialize um JSON sem esse campo, verificando que o valor padrão (0) é usado.
use serde::{Serialize, Deserialize};
use serde_json;
#[derive(Serialize, Deserialize)]
struct Ponto {
x: f64,
y: f64,
}
fn main() {
let p = Ponto { x: 1.0, y: 2.0 };
let json = serde_json::to_string(&p).unwrap();
println!("{}", json);
}use serde::{Deserialize};
use serde_json;
#[derive(Deserialize)]
struct Ponto {
x: f64,
y: f64,
}
fn main() {
let json = r#"{"x":3.5,"y":4.5}"#;
let p: Ponto = serde_json::from_str(json).unwrap();
println!("x: {}, y: {}", p.x, p.y);
}use serde::{Serialize, Deserialize};
use serde_json;
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Usuario {
nome_completo: String,
idade_usuario: u8,
}
fn main() {
let u = Usuario {
nome_completo: "Maria".to_string(),
idade_usuario: 28,
};
let json = serde_json::to_string(&u).unwrap();
println!("{}", json); // {"nomeCompleto":"Maria","idadeUsuario":28}
}use serde::{Serialize, Deserialize};
use serde_json;
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Status {
Ativo,
Inativo,
Pendente,
}
fn main() {
let status = Status::Ativo;
let json = serde_json::to_string(&status).unwrap();
println!("{}", json); // "ativo"
}
use serde::{Deserialize};
use serde_json;
#[derive(Deserialize)]
struct Config {
#[serde(default)]
port: u32,
}
fn main() {
let json = r#"{}"#;
let config: Config = serde_json::from_str(json).unwrap();
println!("port: {}", config.port); // 0
}