Serde
Serialization Library
Serde
Overview
Serde is the de facto standard serialization and deserialization framework for Rust. Using derive macros, it enables efficient and type-safe conversion of Rust data structures to various formats (JSON, YAML, TOML, Bincode, MessagePack, etc.). It achieves high performance through zero-cost abstractions.
Details
Serde, a combination of "Serialization" and "Deserialization," is a framework for efficiently and generically serializing and deserializing Rust data structures.
Key Features:
- Derive Macros: Automatic implementation using
#[derive(Serialize, Deserialize)] - Format Agnostic: Support for multiple data formats with the same code
- Zero-Cost Abstractions: Performance equivalent to hand-written code
- Customizable: Fine-grained control through attribute macros
Technical Details:
- Trait-Based Design: Centered around
SerializeandDeserializetraits - Visitor Pattern: Efficient deserialization implementation
- Type Safety: Safety guaranteed through compile-time type checking
- Generic Support: Handles complex generic types and trait bounds
Supported Formats:
- JSON (serde_json)
- YAML (serde_yaml)
- TOML (toml)
- Bincode (bincode)
- MessagePack (rmp-serde)
- CBOR (serde_cbor)
- Many others
Pros and Cons
Pros
- Standard choice in the Rust ecosystem
- Concise implementation through derive macros
- High performance and memory efficiency
- Rich customization options
- Support for various data formats
- Active community and extensive documentation
Cons
- Somewhat steep learning curve (especially for advanced customization)
- Increased compile times (when using derive macros)
- Error messages can become complex
- Manual implementation required for some complex type conversions
References
- Official Website: https://serde.rs/
- GitHub Repository: https://github.com/serde-rs/serde
- Documentation: https://docs.rs/serde
- The Serde Book: https://serde.rs/
Code Examples
Basic Usage
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point = Point { x: 1, y: 2 };
// Serialize to JSON
let serialized = serde_json::to_string(&point).unwrap();
println!("serialized = {}", serialized);
// Deserialize from JSON
let deserialized: Point = serde_json::from_str(&serialized).unwrap();
println!("deserialized = {:?}", deserialized);
}
Serializing Complex Structures
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug)]
struct User {
id: u64,
name: String,
email: String,
active: bool,
metadata: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Team {
name: String,
members: Vec<User>,
}
fn main() {
let mut metadata = HashMap::new();
metadata.insert("role".to_string(), "admin".to_string());
let user = User {
id: 1,
name: "Alice".to_string(),
email: "[email protected]".to_string(),
active: true,
metadata,
};
let team = Team {
name: "Development".to_string(),
members: vec![user],
};
// Serialize to pretty JSON
let json = serde_json::to_string_pretty(&team).unwrap();
println!("{}", json);
}
Using Custom Attributes
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct ApiResponse {
#[serde(rename = "responseCode")]
response_code: u32,
#[serde(rename = "responseMessage")]
response_message: String,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<String>,
#[serde(default)]
timestamp: u64,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Product {
product_id: u64,
product_name: String,
unit_price: f64,
in_stock: bool,
}
fn main() {
let product = Product {
product_id: 123,
product_name: "Laptop".to_string(),
unit_price: 999.99,
in_stock: true,
};
// Serialized with camelCase
let json = serde_json::to_string(&product).unwrap();
println!("{}", json);
// {"productId":123,"productName":"Laptop","unitPrice":999.99,"inStock":true}
}
Serializing Enums
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
enum Message {
Text { content: String },
Image { url: String, alt_text: String },
Location { lat: f64, lon: f64 },
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum Value {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<Value>),
}
fn main() {
let messages = vec![
Message::Text { content: "Hello!".to_string() },
Message::Image {
url: "https://example.com/image.png".to_string(),
alt_text: "Example image".to_string()
},
Message::Location { lat: 35.6762, lon: 139.6503 },
];
let json = serde_json::to_string_pretty(&messages).unwrap();
println!("{}", json);
}
Implementing Custom Serializers
use serde::{Serialize, Serializer, Deserialize, Deserializer};
use serde::de::{self, Visitor};
use std::fmt;
// Serialize Unix timestamp in human-readable format
struct DateTimeWrapper(chrono::DateTime<chrono::Utc>);
impl Serialize for DateTimeWrapper {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let formatted = self.0.format("%Y-%m-%d %H:%M:%S").to_string();
serializer.serialize_str(&formatted)
}
}
// Custom deserializer
struct DateTimeVisitor;
impl<'de> Visitor<'de> for DateTimeVisitor {
type Value = DateTimeWrapper;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a formatted date time string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match chrono::DateTime::parse_from_str(value, "%Y-%m-%d %H:%M:%S %z") {
Ok(dt) => Ok(DateTimeWrapper(dt.with_timezone(&chrono::Utc))),
Err(_) => Err(E::custom("invalid date format")),
}
}
}
impl<'de> Deserialize<'de> for DateTimeWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(DateTimeVisitor)
}
}
Converting Between Different Formats
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Config {
server: ServerConfig,
database: DatabaseConfig,
}
#[derive(Serialize, Deserialize, Debug)]
struct ServerConfig {
host: String,
port: u16,
workers: usize,
}
#[derive(Serialize, Deserialize, Debug)]
struct DatabaseConfig {
url: String,
max_connections: u32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config {
server: ServerConfig {
host: "localhost".to_string(),
port: 8080,
workers: 4,
},
database: DatabaseConfig {
url: "postgres://localhost/mydb".to_string(),
max_connections: 10,
},
};
// Serialize to JSON
let json = serde_json::to_string_pretty(&config)?;
println!("JSON:\n{}\n", json);
// Serialize to TOML
let toml = toml::to_string_pretty(&config)?;
println!("TOML:\n{}\n", toml);
// Serialize to YAML
let yaml = serde_yaml::to_string(&config)?;
println!("YAML:\n{}", yaml);
Ok(())
}