Rust serde 序列化实战:自定义 derive、枚举互转 JSON 与可选字段处理

test12026-09-245 次阅读

serde 是 Rust 数据交换的底座

无论是读配置文件、写 API 响应还是存消息队列,Rust 里几乎都要靠 serde 在结构体与外部格式(JSON、TOML、YAML)之间转换。最常见的写法是给结构体加 #[derive(Serialize, Deserialize)],再用 serde_json 编解码。但要落地真实业务,往往还需要重命名字段、忽略空值、自定义枚举表示。

基础序列化与字段重命名

很多外部接口用的是 snake_case 或带连字符的字段,和 Rust 命名习惯不一致,用 rename 与 rename_all 对齐即可,不必改结构体字段名。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Order {
    order_id: String,
    total_amount: f64,
    #[serde(rename = "created_at")]
    created_at: i64,
}

fn main() {
    let json = r#"{"orderId":"A1","totalAmount":9.9,"created_at":1700000000}"#;
    let o: Order = serde_json::from_str(json).unwrap();
    println!("{:?}", o);
    let back = serde_json::to_string(&o).unwrap();
    println!("{}", back);
}

枚举与外部标记表示

状态字段常用枚举表达。serde 默认把枚举序列化成对象 {"running": {...}},用 tag + content 可改成字符串或扁平结构,方便和前端约定。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "status", content = "payload")]
enum JobState {
    Pending,
    Running { pid: u32 },
    Failed(String),
}

fn main() {
    let a = JobState::Pending;
    let b = JobState::Running { pid: 42 };
    println!("{}", serde_json::to_string(&a).unwrap()); // {"status":"Pending"}
    println!("{}", serde_json::to_string(&b).unwrap()); // {"status":"Running","payload":{"pid":42}}
}

跳过空值与自定义序列化

写更新接口时,常常希望"未设置的字段不要出现",用 Option 配合 skip_serializing_if = "Option::is_none" 即可自动省略。当某个字段需要特殊格式(例如把时间戳按 RFC3339 输出),可写自定义序列化函数。

use serde::{Serialize, Deserialize, Serializer};

#[derive(Serialize, Deserialize)]
struct Patch {
    name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    avatar: Option<String>,
}

fn upper_hex(v: &u32, s: S) -> Result<S::Ok, S::Error> {
    s.serialize_str(&format!("{:X}", v))
}

#[derive(Serialize)]
struct Token {
    #[serde(serialize_with = "upper_hex")]
    raw: u32,
}

实践要点

  • 用 derive 解决 90% 场景;字段命名冲突用 rename / rename_all,别为了迁就接口改 Rust 字段名。
  • 枚举的 tag/content 模式让"带数据的状态"既能强类型又易与 JSON 互转。
  • 更新类请求用 Option + skip_serializing_if 避免误覆盖未传字段;需要非标准格式时上 serialize_with。
T

test1

文章作者

serde 是 Rust 数据交换的底座 无论是读配置文件、写 API 响应还是存消息队列,Rust 里几乎都要靠 s...

分类
技术
发布时间
2026-09-24
字数
约 2104 字
阅读
5 次