W Rust serializacja i deserializacja danych są dość powszechnymi zadaniami, zwłaszcza podczas integracji z usługami internetowymi, bazami danych lub wymiany wiadomości między komponentami. Najpopularniejsza biblioteka do tego celu to serde, która stała się de facto standardem do pracy z serializacją w Rust.
Historia pytania
Programiści Rust napotkali potrzebę elastycznej i wydajnej serializacji. Początkowo istniały liczne specjalistyczne rozwiązania, ale to właśnie serde zaproponował wygodną integrację z makrami derive, elastyczny schemat wsparcia dla różnych formatów (JSON, CBOR, BSON, TOML, YAML i inne).
Problem
Należy zagwarantować poprawną transformację struktur Rust w formaty wymiany danych (np. JSON) oraz odwrotnie, nie tracąc bezpieczeństwa typów i unikając "cichych błędów" (silent fail). Problemy często wynikają z niespójności struktury danych lub prób serializacji typów, które nie są obsługiwane.
Rozwiązanie
Aby serializować struktury, należy zaimplementować trity Serialize i Deserialize, zazwyczaj za pomocą #[derive(Serialize, Deserialize)]. W przypadku niestandardowych przypadków można zaimplementować te trity ręcznie lub skorzystać z atrybutów, które kontrolują schemat serializacji.
Przykład kodu:
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); }
Kluczowe cechy:
Jak serializować/deserializować pola Option lub pola z wartościami domyślnymi?
Można użyć #[serde(default)] aby wskazać wartość domyślną lub #[serde(skip_serializing_if = "Option::is_none")], aby nie serializować None.
Przykład kodu:
#[derive(Serialize, Deserialize)] struct Config { #[serde(default)] timeout: Option<u32>, }
Czy można serializować struktury z polami customowymi lub wartościami obliczalnymi (np. fn get_hash())?
Tak, ale takie pola należy oznaczyć #[serde(skip)], jeśli nie powinny być serializowane, lub zaimplementować Serialize/Deserialize ręcznie, jeśli chcemy serializować obliczone wartości.
Co się stanie, jeśli struktura w Rust nie zgadza się z polami obiektu JSON (brak pola lub pojawia się nowe)?
Domyślnie serde ignoruje dodatkowe pola w JSON (jeśli nie włączony jest tryb rygorystyczny) i zgłosi błąd w przypadku braku wymaganych pól struktury (jeśli nie jest ustawione #[serde(default)]).
Przybywa zewnętrzny JSON z dodatkowymi i brakującymi polami, a struktura nie używa #[serde(default)],
Zalety:
Wady:
Używane są #[serde(default)] i #[serde(skip_serializing_if)], wszystkie pola są walidowane, dodatkowe są ignorowane.
Zalety:
Wady: