Rust:Serde序列化与反序列化

Serde 是 Rust 生态中序列化/反序列化的事实标准。这篇记录常用的 serde 技巧,包括 derive 宏、字段控制、自定义序列化等。

基本用法

引入依赖:

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

最简单的用法,derive 一下就行:

use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: u64,
    name: String,
    email: String,
}

fn main() {
    let user = User {
        id: 1,
        name: "Alice".into(),
        email: "alice@example.com".into(),
    };

    // 序列化
    let json = serde_json::to_string_pretty(&user).unwrap();
    println!("{}", json);

    // 反序列化
    let parsed: User = serde_json::from_str(&json).unwrap();
    println!("{:?}", parsed);
}

输出:

{
  "id": 1,
  "name": "Alice",
  "email": "alice@example.com"
}

字段重命名

API 经常用 snake_case,但 JSON 可能是 camelCase 或带特殊前缀。#[serde(rename)] 解决单个字段,#[serde(rename_all)] 批量处理:

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ApiResponse {
    status_code: u16,       // -> "statusCode"
    error_message: String,  // -> "errorMessage"
    request_id: String,     // -> "requestId"
}

#[derive(Serialize, Deserialize)]
struct Config {
    #[serde(rename = "type")]  // "type" 是 Rust 关键字
    kind: String,
    
    #[serde(rename = "max-retries")]
    max_retries: u32,
}

rename_all 支持的命名风格:camelCasePascalCasesnake_caseSCREAMING_SNAKE_CASEkebab-case

可选字段与默认值

#[derive(Serialize, Deserialize)]
struct Settings {
    // JSON 中缺少此字段时用 None
    #[serde(default)]
    debug: bool,
    
    // 可选字段,序列化时如果是 None 就不输出
    #[serde(skip_serializing_if = "Option::is_none")]
    proxy: Option<String>,
    
    // 自定义默认值
    #[serde(default = "default_timeout")]
    timeout_ms: u64,
    
    // 缺少时用 Vec 默认值(空数组)
    #[serde(default)]
    tags: Vec<String>,
}

fn default_timeout() -> u64 {
    5000
}

这样就能处理 JSON 字段缺失的情况,不需要手动写 Option 判断。

常用 serde 属性一览

容器级别(#[serde(...)] 放在 struct/enum 上):

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]        // 全局命名转换
#[serde(deny_unknown_fields)]             // 遇到未知字段报错
#[serde(default)]                         // 所有字段使用 Default trait
struct Foo { /* ... */ }

字段级别:

#[derive(Serialize, Deserialize)]
struct Bar {
    #[serde(rename = "type")]             // 重命名
    #[serde(alias = "kind")]              // 反序列化时接受别名
    #[serde(default)]                     // 缺失时用默认值
    #[serde(skip)]                        // 完全跳过
    #[serde(skip_serializing)]            // 只跳过序列化
    #[serde(skip_deserializing)]          // 只跳过反序列化
    #[serde(skip_serializing_if = "Option::is_none")]  // 条件跳过
    #[serde(flatten)]                     // 扁平化嵌套结构
    #[serde(with = "module")]             // 自定义序列化模块
    field: String,
}

枚举序列化

Serde 对枚举的支持很灵活:

// 默认:外部标记 {"variant_name": data}
#[derive(Serialize, Deserialize)]
enum Message {
    Text(String),
    Image { url: String, width: u32 },
    Quit,
}

// 内部标记:{"type": "Text", "content": "hello"}
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum Event {
    Click { x: i32, y: i32 },
    KeyPress { key: String },
    Resize { width: u32, height: u32 },
}

// 相邻标记:{"t": "ok", "c": data}
#[derive(Serialize, Deserialize)]
#[serde(tag = "t", content = "c")]
enum ApiResult {
    Ok(Data),
    Err(String),
}

// 无标记:尝试按顺序匹配每个变体
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum StringOrNumber {
    Str(String),
    Num(f64),
}

自定义序列化

某些类型需要特殊的序列化逻辑,比如把时间戳序列化为指定格式的字符串:

mod datetime_format {
    use chrono::NaiveDateTime;
    use serde::{self, Deserialize, Serializer, Deserializer};

    const FORMAT: &str = "%Y-%m-%d %H:%M:%S";

    pub fn serialize<S>(date: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
    where S: Serializer {
        let s = date.format(FORMAT).to_string();
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDateTime, D::Error>
    where D: Deserializer<'de> {
        let s = String::deserialize(deserializer)?;
        NaiveDateTime::parse_from_str(&s, FORMAT)
            .map_err(serde::de::Error::custom)
    }
}

#[derive(Serialize, Deserialize)]
struct Record {
    id: u64,
    #[serde(with = "datetime_format")]
    created_at: NaiveDateTime,
}

#[serde(with = "module")] 指向自定义模块,模块需要提供 serializedeserialize 两个函数。

Serde 的 derive 宏覆盖了绝大多数场景,很少需要手动实现 Serialize/Deserialize trait。属性系统设计得很完整,基本上想到的需求都有现成的属性可以用。