Serializar Struct para JSON em Rust

Aprenda como serializar structs Rust para JSON com serde. Derive Serialize, to_string, to_writer, pretty print e personalização completa.

Serializar Struct para JSON em Rust

Serializar dados Rust para JSON é essencial para APIs, configurações e troca de dados. A crate serde com serde_json torna isso simples e seguro com a derive macro Serialize.

Dependências

Cargo.toml:

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Serialização básica com Serialize

Adicione #[derive(Serialize)] à sua struct:

use serde::Serialize;

#[derive(Serialize)]
struct Produto {
    nome: String,
    preco: f64,
    em_estoque: bool,
    tags: Vec<String>,
}

fn main() {
    let produto = Produto {
        nome: "Notebook Gamer".to_string(),
        preco: 7499.90,
        em_estoque: true,
        tags: vec!["eletrônico".to_string(), "computador".to_string()],
    };

    // Serializar para String JSON compacta
    let json = serde_json::to_string(&produto).unwrap();
    println!("Compacto: {}", json);

    // Pretty print (formatado)
    let json_bonito = serde_json::to_string_pretty(&produto).unwrap();
    println!("\nFormatado:\n{}", json_bonito);
}

Saída:

Compacto: {"nome":"Notebook Gamer","preco":7499.9,"em_estoque":true,"tags":["eletrônico","computador"]}

Formatado:
{
  "nome": "Notebook Gamer",
  "preco": 7499.9,
  "em_estoque": true,
  "tags": [
    "eletrônico",
    "computador"
  ]
}

Structs aninhadas

Serialize estruturas complexas com structs aninhadas:

use serde::Serialize;

#[derive(Serialize)]
struct Endereco {
    rua: String,
    cidade: String,
    estado: String,
    cep: String,
}

#[derive(Serialize)]
struct Contato {
    tipo_contato: String,
    valor: String,
}

#[derive(Serialize)]
struct Cliente {
    id: u32,
    nome: String,
    endereco: Endereco,
    contatos: Vec<Contato>,
}

fn main() {
    let cliente = Cliente {
        id: 1,
        nome: "Ana Oliveira".to_string(),
        endereco: Endereco {
            rua: "Rua das Flores, 123".to_string(),
            cidade: "São Paulo".to_string(),
            estado: "SP".to_string(),
            cep: "01234-567".to_string(),
        },
        contatos: vec![
            Contato {
                tipo_contato: "email".to_string(),
                valor: "ana@exemplo.com".to_string(),
            },
            Contato {
                tipo_contato: "telefone".to_string(),
                valor: "+55 11 99999-0000".to_string(),
            },
        ],
    };

    let json = serde_json::to_string_pretty(&cliente).unwrap();
    println!("{}", json);
}

Saída:

{
  "id": 1,
  "nome": "Ana Oliveira",
  "endereco": {
    "rua": "Rua das Flores, 123",
    "cidade": "São Paulo",
    "estado": "SP",
    "cep": "01234-567"
  },
  "contatos": [
    {
      "tipo_contato": "email",
      "valor": "ana@exemplo.com"
    },
    {
      "tipo_contato": "telefone",
      "valor": "+55 11 99999-0000"
    }
  ]
}

Personalização com atributos serde

Controle como os campos são serializados:

use serde::Serialize;

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ConfigApi {
    nome_servidor: String,
    porta_http: u16,
    modo_debug: bool,

    #[serde(rename = "maxConexoes")]
    max_conexoes: u32,

    #[serde(skip_serializing_if = "Option::is_none")]
    chave_secreta: Option<String>,

    #[serde(skip)]
    senha_interna: String,

    #[serde(serialize_with = "serializar_como_string")]
    versao: u32,
}

fn serializar_como_string<S>(valor: &u32, serializer: S) -> Result<S::Ok, S::Error>
where
    S: serde::Serializer,
{
    serializer.serialize_str(&format!("v{}.0", valor))
}

fn main() {
    let config = ConfigApi {
        nome_servidor: "api-producao".to_string(),
        porta_http: 8080,
        modo_debug: false,
        max_conexoes: 100,
        chave_secreta: None,             // será omitido no JSON
        senha_interna: "s3cr3t".to_string(), // será ignorado
        versao: 2,
    };

    let json = serde_json::to_string_pretty(&config).unwrap();
    println!("{}", json);

    // Com chave_secreta definida
    let config_com_chave = ConfigApi {
        nome_servidor: "api-dev".to_string(),
        porta_http: 3000,
        modo_debug: true,
        max_conexoes: 10,
        chave_secreta: Some("abc123".to_string()),
        senha_interna: "s3cr3t".to_string(),
        versao: 3,
    };

    let json2 = serde_json::to_string_pretty(&config_com_chave).unwrap();
    println!("\n{}", json2);
}

Saída:

{
  "nomeServidor": "api-producao",
  "portaHttp": 8080,
  "modoDebug": false,
  "maxConexoes": 100,
  "versao": "v2.0"
}

{
  "nomeServidor": "api-dev",
  "portaHttp": 3000,
  "modoDebug": true,
  "maxConexoes": 10,
  "chaveSecreta": "abc123",
  "versao": "v3.0"
}

Serializar para arquivo

Escreva JSON diretamente em um arquivo:

use serde::Serialize;
use std::fs::File;
use std::io::BufWriter;

#[derive(Serialize)]
struct Dados {
    titulo: String,
    registros: Vec<Registro>,
}

#[derive(Serialize)]
struct Registro {
    id: u32,
    valor: f64,
}

fn main() {
    let dados = Dados {
        titulo: "Relatório Mensal".to_string(),
        registros: (1..=5).map(|i| Registro {
            id: i,
            valor: i as f64 * 123.45,
        }).collect(),
    };

    // Serializar direto para arquivo (eficiente — sem String intermediária)
    let arquivo = File::create("relatorio.json").unwrap();
    let escritor = BufWriter::new(arquivo);
    serde_json::to_writer_pretty(escritor, &dados).unwrap();

    println!("JSON salvo em relatorio.json");

    // Verificar o resultado
    let conteudo = std::fs::read_to_string("relatorio.json").unwrap();
    println!("{}", conteudo);
}

Saída:

JSON salvo em relatorio.json
{
  "titulo": "Relatório Mensal",
  "registros": [
    {"id": 1, "valor": 123.45},
    {"id": 2, "valor": 246.9},
    {"id": 3, "valor": 370.35},
    {"id": 4, "valor": 493.8},
    {"id": 5, "valor": 617.25}
  ]
}

Serializar enums

Enums podem ser serializadas de diferentes formas:

use serde::Serialize;

#[derive(Serialize)]
#[serde(tag = "tipo")]
enum Forma {
    #[serde(rename = "circulo")]
    Circulo { raio: f64 },
    #[serde(rename = "retangulo")]
    Retangulo { largura: f64, altura: f64 },
    #[serde(rename = "triangulo")]
    Triangulo { base: f64, altura: f64 },
}

#[derive(Serialize)]
struct Desenho {
    nome: String,
    formas: Vec<Forma>,
}

fn main() {
    let desenho = Desenho {
        nome: "Meu Desenho".to_string(),
        formas: vec![
            Forma::Circulo { raio: 5.0 },
            Forma::Retangulo { largura: 10.0, altura: 20.0 },
            Forma::Triangulo { base: 8.0, altura: 6.0 },
        ],
    };

    let json = serde_json::to_string_pretty(&desenho).unwrap();
    println!("{}", json);
}

Saída:

{
  "nome": "Meu Desenho",
  "formas": [
    {
      "tipo": "circulo",
      "raio": 5.0
    },
    {
      "tipo": "retangulo",
      "largura": 10.0,
      "altura": 20.0
    },
    {
      "tipo": "triangulo",
      "base": 8.0,
      "altura": 6.0
    }
  ]
}

Referência rápida

FunçãoDescrição
to_string()Serializa para String compacta
to_string_pretty()Serializa com indentação
to_writer()Serializa direto para Write (arquivo, buffer)
to_writer_pretty()Serializa formatado para Write
to_vec()Serializa para Vec<u8>

Veja também