In Rust sind die Serialisierung und Deserialisierung von Daten recht häufige Aufgaben, insbesondere bei der Integration mit Webdiensten, Datenbanken oder dem Nachrichtenaustausch zwischen Komponenten. Die bekannteste Bibliothek hierfür ist serde, die de facto zum Standard für die Arbeit mit Serialisierung in Rust geworden ist.
Geschichte der Frage
Rust-Entwickler standen vor der Notwendigkeit einer flexiblen und leistungsfähigen Serialisierung. Anfangs gab es viele spezialisierte Lösungen, aber nur serde bot eine bequeme Integration mit Derive-Makros, ein flexibles Schema zur Unterstützung verschiedener Formate (JSON, CBOR, BSON, TOML, YAML und andere).
Problem
Es ist notwendig, eine korrekte Transformation von Rust-Strukturen in Datenformate (z. B. JSON) und umgekehrt zu garantieren, ohne Typensicherheit zu verlieren und "stille Fehler" (silent fails) zuzulassen. Probleme treten häufig aufgrund von Inkonsistenzen in der Datenstruktur oder dem Versuch auf, nicht unterstützte Typen zu serialisieren.
Lösung
Für die Serialisierung von Strukturen müssen die Traits Serialize und Deserialize implementiert werden, normalerweise mithilfe von #[derive(Serialize, Deserialize)]. In Sonderfällen kann man diese Traits auch manuell implementieren oder Attribute verwenden, die das Serialisierungsschema steuern.
Beispielcode:
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); }
Kernmerkmale:
Wie serialisiert/deserialisiert man Option-Felder oder Felder mit Standardwerten?
Man kann #[serde(default)] verwenden, um einen Standardwert anzugeben, oder #[serde(skip_serializing_if = "Option::is_none")], um None nicht zu serialisieren.
Beispielcode:
#[derive(Serialize, Deserialize)] struct Config { #[serde(default)] timeout: Option<u32>, }
Kann man Strukturen mit benutzerdefinierten Feldern oder berechneten Werten (z. B. fn get_hash()) serialisieren?
Ja, aber solche Felder sollten mit #[serde(skip)] gekennzeichnet werden, wenn sie nicht serialisiert werden sollen, oder manuell Serialize/Deserialize implementiert werden, wenn berechnete Werte serialisiert werden müssen.
Was passiert, wenn die Struktur in Rust nicht mit dem JSON-Objekt übereinstimmt (ein Feld fehlt oder ein neues erscheint)?
Standardmäßig ignoriert serde zusätzliche Felder im JSON (es sei denn, der strenge Modus ist aktiviert) und gibt einen Fehler aus, wenn erforderliche Felder in der Struktur fehlen (es sei denn, #[serde(default)] ist angegeben).
Ein externes JSON kommt mit zusätzlichen und fehlenden Feldern, und die Struktur verwendet nicht #[serde(default)],
Vorteile:
Nachteile:
Verwendet #[serde(default)] und #[serde(skip_serializing_if)], alle Felder werden validiert, überflüssige werden ignoriert.
Vorteile:
Nachteile: