In Rust, la serializzazione e deserializzazione dei dati sono compiti abbastanza frequenti, specialmente quando si integra con servizi web, database o scambi di messaggi tra componenti. La libreria più popolare per questo scopo è serde, che è diventata di fatto lo standard de facto per lavorare con la serializzazione in Rust.
Storia della questione
Gli sviluppatori di Rust si sono trovati di fronte alla necessità di una serializzazione flessibile e performante. Inizialmente esistevano molte soluzioni specializzate, ma serde ha proposto un'integrazione comoda con i macro derive, uno schema flessibile per supportare diversi formati (JSON, CBOR, BSON, TOML, YAML e altri).
Problema
È necessario garantire la corretta trasformazione delle strutture Rust nei formati di scambio dati (ad esempio JSON) e viceversa, senza perdere la sicurezza dei tipi e senza permettere "errori silenziosi" (silent fail). I problemi sono spesso causati da incoerenze nella struttura dei dati o tentativi di serializzare tipi non supportati.
Soluzione
Per serializzare le strutture è necessario implementare i trait Serialize e Deserialize, di solito con #[derive(Serialize, Deserialize)]. Per casi non standard, è possibile implementare manualmente questi trait o utilizzare attributi che controllano lo schema di serializzazione.
Esempio di codice:
use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Debug)] struct Person { name: String, age: u8, } fn main() { let data = Person { name: "Bob".into(), age: 32 }; let json = serde_json::to_string(&data).unwrap(); println!("{}", json); let obj: Person = serde_json::from_str(&json).unwrap(); println!("{:?}", obj); }
Caratteristiche chiave:
Come serializzare/deserializzare campi Option o campi con valori predefiniti?
Si può utilizzare #[serde(default)] per specificare un valore predefinito o #[serde(skip_serializing_if = "Option::is_none")], per non serializzare None.
Esempio di codice:
#[derive(Serialize, Deserialize)] struct Config { #[serde(default)] timeout: Option<u32>, }
È possibile serializzare strutture con campi custom o valori calcolati (ad esempio fn get_hash())?
Sì, ma tali campi devono essere contrassegnati con #[serde(skip)], se non devono essere serializzati, oppure implementare Serialize/Deserialize manualmente, se è necessario serializzare valori calcolati.
Cosa succede se la struttura in Rust non corrisponde per campi all'oggetto JSON (un campo è assente o appare uno nuovo)?
Per default, serde ignora i campi in eccesso in JSON (se non è attivata la modalità rigorosa) e restituirà un errore in caso di assenza di campi obbligatori della struttura (se non è specificato #[serde(default)]).
Arriva un JSON esterno con campi in eccesso e mancanti, e la struttura non utilizza #[serde(default)],
Pro:
Contro:
Si utilizza #[serde(default)] e #[serde(skip_serializing_if)], tutti i campi vengono validati, i campi in eccesso vengono ignorati.
Pro:
Contro: