序列化与反序列化在某些编程语言中一直是让人头疼的问题。现在得益于 Rust 语言优秀的设计,社区诞生了 Serde 这一大杀器,让序列化与反序列化变得前所未有的统一、简单与优雅。
一句话概括,序列化(serialization)就是将内存中的数据结构转换成可以存储或传输的字节流或字符串的过程。
而反序列化(deserialization)就是倒过来,将字节流或字符串还原成内存中的数据结构。
在代码层面,Serde 数据模型的序列化部分由 Serializer 特征定义,反序列化部分则由 Deserializer 特征定义。它们提供了一种将每个 Rust 数据结构映射到 29 种可能类型之一的方式。Serializer 特征的每个方法都对应数据模型中的一种类型。
当从某种格式反序列化一个数据结构时,数据结构的 Deserialize 实现负责通过向 Deserializer 传递一个 Visitor 实现来将数据结构映射到 Serde 数据模型,该 Visitor 实现能够接收数据模型的各种类型;而数据格式的 Deserializer 实现负责通过调用且仅调用一个 Visitor 方法,将输入数据映射到 Serde 数据模型。
Serde 数据模型是 Rust 类型系统的简化形式。它包含以下 29 种类型:
booli8, i16, i32, i64, i128u8, u16, u32, u64, u128f32, f64char[u8]
() 的类型。它表示一个不包含任何数据的匿名值。struct Unit 或 PhantomData<T>。它表示一个不包含任何数据的具名值。enum E { A, B } 中的 E::A 和 E::B。struct Millimeters(u8)。enum E { N(u8) } 中的 E::N。Vec<T> 或 HashSet<T>。序列化时,长度可能在遍历所有数据之前已知,也可能未知。反序列化时,长度通过查看序列化数据来确定。请注意,像 vec![Value::Bool(true), Value::Char('c')] 这样的同质 Rust 集合可能序列化为异质的 Serde 序列,在此例中包含一个 Serde 布尔值后跟一个 Serde 字符。(u8,) 或 (String, u64, Vec<T>) 或 [u64; 10]。struct Rgb(u8, u8, u8)。enum E { T(u8, u8) } 中的 E::T。BTreeMap<K, V>。序列化时,长度可能在遍历所有条目之前已知,也可能未知。反序列化时,长度通过查看序列化数据来确定。struct S { r: u8, g: u8, b: u8 }。enum E { S { r: u8, g: u8, b: u8 } } 中的 E::S。对于大多数 Rust 类型,它们到 Serde 数据模型的映射是直接的。例如,Rust 的 bool 类型对应于 Serde 的 bool 类型。Rust 的元组结构体 Rgb(u8, u8, u8) 对应于 Serde 的元组结构体类型。
但这些映射并不一定要是直接的。Serialize 和 Deserialize 特征可以在 Rust 类型和 Serde 数据模型之间执行任何适合用例的映射。
以 Rust 的 std::ffi::OsString 类型为例。此类型表示平台原生的字符串。在 Unix 系统上,它们是任意的非零字节;在 Windows 系统上,它们是任意的非零 16 位值。将 OsString 映射到 Serde 数据模型,以下类型似乎是自然的选择:
OsString 不能保证可以用 UTF-8 表示;反序列化也会很不稳定,因为 Serde 字符串允许包含 0 字节。OsString 并在 Windows 上反序列化它,最终会得到错误的字符串。相反,OsString 的 Serialize 和 Deserialize 实现通过将 OsString 视为 Serde 枚举来映射到 Serde 数据模型。实际上,它的行为就好像 OsString 被定义为以下类型一样,尽管这与它在任何单个平台上的定义都不匹配。
enum OsString {
Unix(Vec<u8>),
Windows(Vec<u16>),
// 以及其他平台
}
映射到 Serde 数据模型时的这种灵活性是深刻而强大的。在实现 Serialize 和 Deserialize 时,要注意你的类型所处的更广泛上下文,最直观的映射可能并非最佳选择。
社区提供了一系列基于 Serde 或兼容 Serde 的库,包括 serde_json、serde_yaml、toml 等。它们为开发者提供了多种序列化方案。
| 库 | 说明 |
|---|---|
serde_json | 处理 JSON 格式 |
serde_yaml | 处理 YAML 格式,现已停止维护 |
serde_saphyr | 处理 YAML 格式 |
toml | 处理 TOML 格式 |
serde-xml-rs | 处理 XML 格式 |
ciborium | 处理 CBOR 格式,现已停止维护 |
这里我们着重介绍 serde_json,它与 Serde 深度绑定,提供了一系列用于将 Serde 数据序列化为 JSON 格式或将 JSON 反序列化为 Serde 数据的方法。
let data = r#"
{
"name": "John Doe",
"age": 43,
"phones": [
"+44 1234567",
"+44 2345678"
]
}"#;
// 把符合 JSON 格式的字符串解析为 `serde_json::Value`,该类型能被轻易地序列化或反序列化
let v: Value = serde_json::from_str(data).unwrap();
// 像访问 JSON 那样访问 `serde_json::Value`
println!("Please call {} at the number {}", v["name"], v["phones"][0]);
另外它还提供一个 json 宏,其允许你“所见即所得”地直接创建 serde_json::Value。
let code = 200;
let features = vec!["serde", "json"];
let value = json!({
"code": code, // 变量或常量
"success": code == 200, // 表达式
"payload": {
features[0]: features[1] // 数组元素
}
});
需要注意的是,json 宏要求其中的任何值都必须实现了 Serialize 特征。
Serde 提供了派生宏,用于为你定义的数据结构快速生成 Serialize 和 Deserialize 特征的实现。就像你用于自动派生内置 Clone、Copy、Debug 或其他特征的实现一样。
它能够为大多数结构体和枚举生成实现,包括那些带有复杂泛型类型或特征约束的类型。在极少数情况下,对于特别复杂的类型,你可能需要手动实现这些特征。
记得在 Cargo.toml 中为 serde 添加 features = ["derive"]。然后你就可以畅快地为数据结构派生特征了:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct Point {
x: i32,
y: i32,
}
fn main() {
let point = Point { x: 1, y: 2 };
let serialized = serde_json::to_string(&point).unwrap();
println!("serialized = {}", serialized);
// 输出 serialized = {"x":1,"y":2}
// 表明序列化成功
let deserialized: Point = serde_json::from_str(&serialized).unwrap();
println!("deserialized = {:?}", deserialized);
// 输出 deserialized = Point { x: 1, y: 2 }
// 表明反序列化成功
}
在后续介绍其他派生宏时,默认都使用 serde_json 的序列化结果来演示。
如果只使用派生宏来实现 Serialize 和 Deserialize 特征,灵活性并不高。所幸 Serde 提供了一系列用于指定属性的派生宏,从而大幅增强了派生的灵活程度。
下面介绍一些常用的派生宏:
#[serde(rename = "name")] 使用给定的名称(而非其 Rust 名称)来序列化和反序列化结构体(或其字段)或枚举(或其变体)。
#[derive(Serialize, Deserialize, Debug)]
struct Point {
#[serde(rename = "x_coordinate")]
x: i32,
y: i32,
}
let point = Point { x: 1, y: 2 };
// 序列化为 {"x_coordinate":1,"y":2}
#[derive(Serialize, Deserialize, Debug)]
enum CoordSystem {
#[serde(rename = "CartesianCoord")]
Cartesian,
Polar,
Spherical,
}
let sys = CoordSystem::Cartesian;
// 序列化为 "CartesianCoord"
#[serde(rename(serialize = "ser_name"))]
#[serde(rename(deserialize = "de_name"))]
#[serde(rename_all = "...")] 根据给定的命名规范重命名所有字段(如果是结构体)或所有变体(如果是枚举)。
| 可选值 | 说明 |
|---|---|
lowercase | 全小写 |
UPPERCASE | 全大写 |
PascalCase | 每个单词首字母大写 |
camelCase | 首单词首字母小写,后续单词首字母大写 |
snake_case | 单词间用下划线连接,全小写 |
SCREAMING_SNAKE_CASE | 单词间用下划线连接,全大写 |
kebab-case | 单词间用连字符连接,全小写 |
SCREAMING-KEBAB-CASE | 单词间用连字符连接,全大写 |
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Point {
x_coord: i32,
y_coord: i32,
z_coord: i32,
}
let point = Point { x_coord: 1, y_coord: 2, z_coord: 3 };
// 序列化为 {"XCoord":1,"YCoord":2,"ZCoord":3}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
enum CoordSystem {
CartesianCoord,
PolarCoord,
SphericalCoord,
}
let sys = CoordSystem::CartesianCoord;
// 序列化为 "cartesian-coord"
#[serde(skip)] 跳过此字段(如果是结构体)或变体(如果是枚举),既不序列化也不反序列化它。
#[derive(Serialize, Deserialize, Debug)]
pub struct Point {
x: i32,
#[serde(skip)]
y: i32,
z: i32,
}
let point = Point { x: 1, y: 2, z: 3 };
// 序列化为 {"x":1,"z":3}
结构体的反序列化时,Serde 将始终使用 Default::default() 或由 default = "..." 指定的函数来获取此字段的默认值。
let str = r#"
{"x":1,"y":2,"z":3}
"#;
let p: Point = serde_json::from_str(&str).unwrap();
println!("{:?}", p); // 输出 Point { x: 1, y: 0, z: 3 }
// 反序列化时自动跳过了字段 `y`
// 并用 `i32` 默认值(即 `0`)代替
#[serde(default)] 表示如果反序列化时该值不存在,则使用 Default::default()。
#[derive(Serialize, Deserialize, Debug)]
pub struct Point {
x: i32,
#[serde(default)]
y: i32,
z: i32,
}
let str = r#"
{"x":1,"z":3}
"#;
// 反序列化为 Point { x: 1, y: 0, z: 3 }
要注意
#[serde(default)]与#[serde(skip)]在反序列化时的区别。
#[serde(default = "path")] 表示如果反序列化时该值不存在,则调用指定的 path 函数来获取默认值。
fn() -> T。例如 default = "empty_value" 将调用 empty_value(),default = "SomeTrait::some_default" 将调用 SomeTrait::some_default()。#[derive(Serialize, Deserialize, Debug)]
pub struct Point {
x: i32,
#[serde(default = "Point::default_y")]
y: i32,
z: i32,
}
impl Point {
fn default_y() -> i32 {
i32::MAX
}
}
let str = r#"
{"x":1,"z":3}
"#;
// 反序列化为 Point { x: 1, y: 2147483647, z: 3 }
#[serde(flatten)] 将此字段的内容展平到其所在的容器中,从而消除了序列化表示与 Rust 数据结构表示之间的一层结构。
deny_unknown_fields 的结构体同时使用。#[derive(Serialize, Deserialize)]
struct Pagination {
limit: u32,
offset: u32,
total: u32,
}
#[derive(Serialize, Deserialize)]
struct Users {
user: String,
#[serde(flatten)]
pagination: Pagination,
}
let u = Users {
user: String::from("Admin"),
pagination: Pagination { limit: 500, offset: 42, total: 542 }
};
// 序列化为 {"user":"Admin","limit":500,"offset":42,"total":542}
// 如果去掉 `#[serde(flatten)]`
// 则序列化结果变成
// {"user":"Admin","pagination":{"limit":500,"offset":42,"total":542}}
#[serde(untagged)] 仅对枚举变体的数据序列化,而不序列化变体名称。
#[derive(Serialize, Deserialize, Debug)]
enum CoordSystem {
CartesianCoord,
PolarCoord,
SphericalCoord,
#[serde(untagged)]
OtherCoord(String),
}
let p = CoordSystem::OtherCoord(String::from("Cylindrical"));
// 序列化为 "Cylindrical"
// 而不是 {"OtherCoord":"Cylindrical"}
#[serde(from = "FromType")] 通过先反序列化为 FromType,再进行转换的方式来反序列化此类型。此类型必须实现 From<FromType>,且 FromType 必须实现 Deserialize。
#[serde(try_from = "FromType")],它会进行可失败的转换,其错误类型必须实现 Display。#[serde(into = "IntoType")] 通过将此类型转换为指定的 IntoType 并序列化 IntoType 类型的方式来序列化此类型。此类型必须实现 Clone 和 Into<IntoType>,且 IntoType 必须实现 Serialize。
Serde 所提供的派生宏功能已经足够强大,以至于大多数情况下我们完全不需要手动去实现 Serialize 和 Deserialize。但在某些极端场景下,可能需要手动对序列化进行高度定制。
首先来看看这两个特征:
pub trait Serialize {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer;
}
pub trait Deserialize<'de>: Sized {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>;
}
我们看到,在特征中又出现了
Serializer和Deserializer两个新特征。它们和Serialize、Deserialize的关系是什么?实际上,
Serialize、Deserialize定义数据结构的序列化/反序列化逻辑。由需要被序列化的类型实现。而Serializer和Deserializer则是定义了序列化和反序列化的具体格式(例如 JSON、YAML 等),一般由序列化库(如serde_json、serde_yaml等)实现。具体来说,对于一个完整的序列化过程(从数据结构到字符串),会发生:
- 用户通过库提供的函数(如
serde_json::to_string)来开始序列化过程;- 库提供的函数内部调用类型实现的
serialize方法;serialize方法接收库提供的实现Serializer的类型;- 实现
Serializer的类型实例会提供多种方法(例如serialize_i32、serialize_tuple、serialize_struct等)来将数据结构进行具体的序列化;- 多种方法将序列化产生的字符串输出到缓冲区;
- 缓冲区的字符串被层层传回,最后由库提供的函数返回。
对于 Serde 生态中成熟的序列化库来说,我们并不需要实现 Serializer 和 Deserializer,只需要对类型实现 Serialize、Deserialize。但是我们需要熟悉 Serializer 和 Deserializer 提供的一些方法。例如: