十分钟掌握 Rust json 序列化工具

引言

这是Rust九九八十一难第九篇,介绍下serde json序列化相关知识点。在web/移动端开发中,json几乎是应用最广泛的格式,因此掌握序列化json是必要的。Rust中最成熟的库是serde_json。它基于 serde 框架构建,提供了高性能、安全且类型安全的 JSON 序列化与反序列化功能。

一、入门API使用示例

1、添加依赖

yaml 复制代码
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" #必要的
chrono = { version = "0.4", features = ["serde"] } # 时间示例使用

serde:核心库

serde_derive:(通过 features = ["derive"] 激活): 提供 `#[derive(Serialize, Deserialize)]

serde_json:支持 JSON 格式(类似 Java 中 Jackson JSON 模块),官网地址:docs.rs/serde_json/...

2、序列化 Rust 结构体

rust 复制代码
use serde::{Serialize, Deserialize};
use serde_json;

#[derive(Serialize, Deserialize, Debug)]
struct User {
    id: u32,
    name: String,
    active: bool,
}

fn main() {
    let user = User { id: 1, name: "Alice".into(), active: true };

    // 转为紧凑 JSON
    let json_str = serde_json::to_string(&user).unwrap();
    println!("{}", json_str);

    // 转为漂亮的 JSON
    let pretty_json = serde_json::to_string_pretty(&user).unwrap();
    println!("{}", pretty_json);
}

说明:这是最简单的用法,用于保存配置、API 返回序列化数据、日志输出等。编译期生成代码,初次编译可能稍慢

3、JSON 反序列化到结构体

rust 复制代码
fn main() {
    let json_data = r#"{"id":2,"name":"Bob","active":false}"#;
    let user: User = serde_json::from_str(json_data).unwrap();
    println!("{:?}", user);
}

说明 :类型安全,支持可选字段 (Option<T>),JSON 字段必须匹配结构体(可通过 #[serde(rename)] 调整)

4、动态 JSON (Value) 访问

rust 复制代码
use serde_json::{Value, json};

fn main() {
    let data: Value = json!({
        "user": { "name": "Alice", "age": 25 },
        "tags": ["rust", "serde"]
    });

    let user_name = data["user"]["name"].as_str().unwrap_or("unknown");
    let first_tag = data["tags"][0].as_str().unwrap_or_default();
    println!("Name: {}, First tag: {}", user_name, first_tag);
}

说明 :适合处理未知结构 JSON、动态字段或可选字段。运行时才会发现类型错误,频繁访问深层字段需要 unwrap / Option 处理。

5、JSON 构建 (json!() 宏)

rust 复制代码
fn main() {
    let dynamic_json = json!({
        "id": 1001,
        "name": "Charlie",
        "roles": ["admin", "user"]
    });

    println!("{}", dynamic_json.to_string());
}

说明:简洁,支持动态构造,可嵌套对象/数组,适用于快速生成测试数据、API payload、日志结构。

6、文件读写

rust 复制代码
use std::fs::File;

fn main() {
    let file = File::open("data.json").unwrap();
    let data: Value = serde_json::from_reader(file).unwrap();

    let output = File::create("output.json").unwrap();
    serde_json::to_writer_pretty(output, &data).unwrap();
}

说明:直接读写文件,简单高效,文件过大时需要注意内存占用

二、常用特性

1、设置可选字段

示例:option null处理和default默认值

rust 复制代码
#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    #[serde(default)]
    age: u8,
    #[serde(skip_serializing_if = "Option::is_none")]
    email: Option<String>,
}

fn main() {
 		let u = Person { name: "bob".into(), age: 0,  email: None };
    let json = serde_json::to_string(&u).unwrap();
    println!("{}", json); // 输出: {"username":"bob"}
   	
    //f反序列化
    let json_data = r#"{"user_name":"bob"}"#;
    let user: Person = serde_json::from_str(json_data).unwrap();
    println!("{:?}", user);//Person { name: "bob", age: 0, password: "", email: None }
}
  • skip / skip_serializing_if:跳过某些字段

  • Option<T> 字段在序列化时,如果是 None,默认会输出 null。 可以使用 #[serde(skip_serializing_if = "Option::is_none")] 忽略 None 字段。

  • 序列化 JSON 时字段名为 user_name反序列化时也能正确匹配

  • 当 JSON字符串 中缺少age字段时,会使用 Default::default() 的值

2、设置重命名

rename / rename_all:修改 JSON 字段名,解决json字段与struct字段不一致问题

rust 复制代码
#[derive(Serialize, Deserialize, Debug)]
struct User {
		#[serde(rename = "user_name")]
    name: String,
    #[serde(skip)]              // 永远不序列化/反序列化
    password: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    email: Option<String>,
}

let u = User { username: "bob".into(), password: "123".into(), email: None };
let json = serde_json::to_string(&u).unwrap();
println!("{}", json); // 输出: {"username":"bob"}

示例:整体转换为驼峰格式

rust 复制代码
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Status {
    Active,
    Inactive,
}

3、自定义序列化逻辑

  • 有些字段需要 特殊转换(比如时间戳、枚举映射、加密字段)
  • 可以使用 #[serde(with = "...")] 或手动实现 Serialize / Deserialize

示例:序列化 chrono::DateTime 为时间戳

rust 复制代码
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};

#[derive(Serialize, Deserialize)]
struct LogEntry {
    #[serde(serialize_with = "my_serializer", deserialize_with = "my_deserializer")]
    timestamp: chrono::DateTime<Utc>,
}

fn my_serializer<S>(dt: &chrono::DateTime<Utc>, s: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
    let formatted = dt.format("%d/%m/%Y %H:%M:%S").to_string();
    s.serialize_str(&formatted)
}

fn my_deserializer<'de, D>(d: D) -> Result<chrono::DateTime<Utc>, D::Error>
where D: serde::Deserializer<'de> {
    let s = String::deserialize(d)?;
    chrono::DateTime::parse_from_str(&s, "%d/%m/%Y %H:%M:%S")
        .map(|dt| dt.with_timezone(&Utc))
        .map_err(serde::de::Error::custom)
}
  • 输出示例:{"timestamp":"29/10/2025 21:45:00"}

4、枚举映射

a、默认示例

serde 默认会把 枚举的变体名 转成字符串

rust 复制代码
use serde::{Serialize, Deserialize};
use serde_json;

#[derive(Serialize, Deserialize, Debug)]
enum Color {
    Red,
    Green,
    Blue,
}

fn main() {
    let c = Color::Green;

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

    // 反序列化
    let back: Color = serde_json::from_str(&json).unwrap();
    println!("Deserialized: {:?}", back);
}
//Serialized: "Green"
//Deserialized: Green
b、带字段的枚举(结构化枚举)

serde 的默认结构外层是变体名,内层是对应字段

rust 复制代码
use serde::{Serialize, Deserialize};
use serde_json;

#[derive(Serialize, Deserialize, Debug)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
}

fn main() {
    let s = Shape::Rectangle { width: 3.0, height: 4.0 };

    let json = serde_json::to_string_pretty(&s).unwrap();
    println!("Serialized:\n{}", json);

    let back: Shape = serde_json::from_str(&json).unwrap();
    println!("Deserialized: {:?}", back);
}

打印结果:

json 复制代码
{
  "Rectangle": {
    "width": 3.0,
    "height": 4.0
  }
}
c、自定义标签风格(Tagged Enum)

type是枚举值名称,data是携带的数据

rust 复制代码
use serde::{Serialize, Deserialize};
use serde_json;

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", content = "data")]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
}

fn main() {
    let s = Shape::Circle { radius: 5.0 };
    let json = serde_json::to_string_pretty(&s).unwrap();
    println!("Serialized:\n{}", json);
}

打印结果

json 复制代码
{
  "type": "Circle",
  "data": {
    "radius": 5.0
  }
}
d、枚举转数字(或字符串映射)

有时数据库或前端返回的不是字符串,而是数字或自定义字符串。 比如:1 -> Red2 -> Green

rust 复制代码
use serde::{Serialize, Deserialize, Serializer, Deserializer};
use serde::de::Error;

#[derive(Debug)]
enum Color {
    Red,
    Green,
    Blue,
}

impl Serialize for Color {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let code = match self {
            Color::Red => 1,
            Color::Green => 2,
            Color::Blue => 3,
        };
        serializer.serialize_u8(code)
    }
}

impl<'de> Deserialize<'de> for Color {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let v = u8::deserialize(deserializer)?;
        match v {
            1 => Ok(Color::Red),
            2 => Ok(Color::Green),
            3 => Ok(Color::Blue),
            _ => Err(D::Error::custom(format!("invalid color code: {}", v))),
        }
    }
}

fn main() {
    let c = Color::Green;
    let json = serde_json::to_string(&c).unwrap();
    println!("Serialized: {}", json); // 2

    let back: Color = serde_json::from_str("3").unwrap();
    println!("Deserialized: {:?}", back); // Blue
}
e、枚举空值容忍(Option + Default)

如果 JSON 里没有对应字段或字段为空,可以用 Option

rust 复制代码
#[derive(Serialize, Deserialize, Debug)]
struct Item {
    name: String,
    color: Option<Color>, // 可空枚举
}

三、配合其他库

1、JSON 与数据库对象映射

直接将查询结果映射为 JSON,可快速生成 API 响应

rust 复制代码
use serde::{Serialize, Deserialize};
use sqlx::FromRow;

#[derive(Serialize, Deserialize, FromRow)]
struct User {
    id: i32,
    name: String,
}

async fn fetch_users(pool: &sqlx::PgPool) -> serde_json::Value {
    let users: Vec<User> = sqlx::query_as::<_, User>("SELECT id, name FROM users")
        .fetch_all(pool).await.unwrap();
    serde_json::to_value(users).unwrap()
}

2、JSON Body 序列化(POST / PUT 请求)

rust 复制代码
use serde::Serialize;
use reqwest::Client;

#[derive(Serialize)]
struct LoginRequest {
    username: String,
    password: String,
}

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = Client::new();
    let req_body = LoginRequest {
        username: "alice".into(),
        password: "123456".into(),
    };

    let resp = client
        .post("https://example.com/login")
        .json(&req_body) // 自动序列化为 JSON
        .send()
        .await?;

    let text = resp.text().await?;
    println!("{}", text);

    Ok(())
}

3、serde_with辅助工具

简介: serde_with 是一个成熟的 Rust crate,提供多种 辅助宏和序列化策略,可以方便地处理常见复杂场景,包括:

  • 跳过空字段、默认值
  • 日期 / 时间格式转换
  • 集合类型映射(如 Vec ↔ Map)
  • 循环引用或复杂嵌套结构的自定义序列化

特点:

  • 提供宏和组合属性,减少手动实现 Serialize / Deserialize
  • 可直接与 serde 集成,保持类型安全

示例:序列化 Option 和默认值

rust 复制代码
use serde::{Serialize, Deserialize};
use serde_with::{serde_as, DefaultOnNull};

#[serde_as]
#[derive(Serialize, Deserialize)]
struct User {
    id: u32,
    #[serde_as(as = "DefaultOnNull")]
    name: String, // 如果 JSON 为 null,会用默认值 ""
}

fn main() {
    let json = r#"{"id":1,"name":null}"#;
    let user: User = serde_json::from_str(json).unwrap();
    println!("name: {}", user.name); // 输出 ""
}

示例:简化复杂引用/嵌套结构处理, 解决循环引用

rust 复制代码
use serde_with::skip_serializing_none;
use serde::{Serialize, Deserialize};

#[skip_serializing_none]
#[derive(Serialize, Deserialize)]
struct Node {
    value: i32,
    parent: Option<Box<Node>>, // 嵌套可能为空的父节点
}

4、字典(Map)排序(稳定序列化用于签名或对比)

方案 1 --- 在序列化前对 map key 做排序(手动)

rust 复制代码
use serde_json::Map;
let mut v: serde_json::Map<String, serde_json::Value> = ...;
let mut keys: Vec<_> = v.keys().cloned().collect();
keys.sort();
let mut ordered = serde_json::Map::new();
for k in keys { ordered.insert(k.clone(), v.remove(&k).unwrap()); }
let s = serde_json::to_string(&ordered)?;

方案 2 --- 使用 BTreeMap(天然有序)

rust 复制代码
use std::collections::BTreeMap;
let mut m: BTreeMap<String, i32> = BTreeMap::new();
m.insert("b".into(), 2);
m.insert("a".into(), 1);
let s = serde_json::to_string(&m)?; // keys 按字典序序列化

若需要签名/稳定化输出,用 BTreeMap 更简单且高效。

5、复杂json序列化辅助报错

方案 1 --- 普通方式

rust 复制代码
use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct User {
    name: String,
    age: u32,
}

#[derive(Deserialize, Debug)]
struct Config {
    user: User,
}
let json_data = r#"{"user": {"name": "Alice", "age": "not-a-number"}}"#;
let cfg: Config = serde_json::from_str(json_data)?;

会报错:

go 复制代码
invalid type: string "not-a-number", expected u32

但我们不知道错误在哪个字段。

方案2 --- serde_path_to_error

rust 复制代码
use serde_path_to_error::deserialize;
use serde_json::Deserializer;

fn main() {
    let json_data = r#"{"user": {"name": "Alice", "age": "not-a-number"}}"#;
    let mut deserializer = Deserializer::from_str(json_data);
    let result: Result<Config, _> = deserialize(&mut deserializer);

    match result {
        Ok(_) => println!("OK"),
        Err(err) => {
            let path = err.path().to_string();
            println!("❌ Error at path: {}", path);
            println!("Cause: {}", err);
        }
    }
}
lua 复制代码
❌ Error at path: user.age
Cause: invalid type: string "not-a-number", expected u32

这样你就能立刻定位到错误在 user.age

四、小结

本篇对Rust json介绍了序列化和反序列化,总结了一批例子,涵盖入门到高级和配合其他库使用。从这些例子能看到Rust 的不太灵活,但是更偏「编译期安全 + 手动控制」。

如果喜欢,请点个关注吧,本人公众号大鱼七成饱

相关推荐
Source.Liu5 小时前
【PrintPDF】PrintPDF Cargo.toml 分析
rust·pdf
Source.Liu5 小时前
【printpdf】生成PDF的全能Rust库printpdf
rust·pdf
三翼鸟数字化技术团队6 小时前
Rust指北之锁
rust
_安晓6 小时前
Rust Link-Time Optimization(LTO):跨编译单元优化的深度剖析
rust
初学者,亦行者6 小时前
Rust 模式匹配的穷尽性检查:从编译器证明到工程演进
后端·rust·django
Aogu1817 小时前
Rust 中 WebSocket 支持的实现:从协议到生产级应用
rust
四念处茫茫7 小时前
Rust:与JSON、TOML等格式的集成
java·rust·json
微知语8 小时前
Cell 与 RefCell:Rust 内部可变性的双生子解析
java·前端·rust
晨陌y8 小时前
从 0 到 1 开发 Rust 分布式日志服务:高吞吐设计 + 存储优化,支撑千万级日志采集
开发语言·分布式·rust