引言
这是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 -> Red、2 -> 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 的不太灵活,但是更偏「编译期安全 + 手动控制」。
如果喜欢,请点个关注吧,本人公众号大鱼七成饱。
