Serde

serializationRustdeserializationderive-macrotype-safety

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 Serialize and Deserialize traits
  • 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

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(())
}