在处理HTTP请求时,我们总是需要在数据结构对象(可以是enum、struct等)和序列化数据格式(例如JSON,用与存储或传输,并可以反序列化的格式)之间来回转换。
Serde是一个库(crate),用于高效、通用地序列化和反序列化Rust数据结构。在本文中,我将向您展示如何使用Attributes自定义Serde派生生成的序列化和反序列化实现。
入门示例
-
增加依赖
在项目目录中执行命令:
bashcargo add serde --features derive cargo add serde_json
cargo add 命令自动增加指定包至Cargo.toml文件。serde包使用--features derive
标识,启用强大的派生特性;serde_json包处理Json序列化和反序列化。执行命令之后,Cargo.toml文件被更新:
[dependencies]
serde = { version = "1.0.nnn", features = ["derive"] }
serde_json = "1.0.nnn"
让我们从一个简单的结构体Student开始,它的定义如下所示,并初始化我们的第一个学生tom。
rust
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Person {
name: String,
age: u32,
email: String,
}
fn main() {
let person = Person {
name: "John Doe".to_string(),
age: 30,
email: "john@example.com".to_string(),
};
let json = serde_json::to_string(&person).unwrap();
println!("{}", json);
}
输出将如下所示:
json
{
"name":"John Doe",
"age":30,
"email":"john@example.com"
}
看起来太棒了!但是,实际应用中,生成json需要适用不同场景,如重新命名字段、忽略部分字段、拉平嵌套对象等。下面分别进行举例说明。
命名规范
例如,我们实际上希望使用studentId而不是student_id作为字段名。
- 字段重命名
使用rename对单个字段进行重命名:
rust
struct Student {
pub name: String,
#[serde(rename="studentId")
pub student_id: String,
}
- 命名规范
使用rename_all 实现整个struct遵循骆驼命名规则:
rust
#[serde(rename_all = "camelCase")]
struct Student {
pub name: String,
pub student_id: String,
}
除了camelCase之外,您还可以应用其他的case惯例。取值为小写、大写、PascalCase、camelCase、snake_case、SCREAMING_SNAKE_CASE、kebab-case、scream - kebab-case。
您可能想知道的另一件事是,为什么要重命名字段呢?如果所需的字段名是一个保留的Rust关键字类型,那么它是非常有用的。另一个有用的地方是当您使用enum并且希望使用特定名称在外部标记它时。我们很快就会讲到这个。
忽略字段
Skip可用于不希望序列化或反序列化的字段。下面是一个简单的例子。让我们给Student添加birth_year和age。
rust
struct Student {
pub name: String,
pub student_id: String,
pub birth_year: u32,
pub age: u32,
}
我们可能希望动态更新年龄,因此需要对学生的birth_year的引用。但是,当我们发送请求时,应该只显示age字段。这可以使用#[serde(skip)]来解决。
rust
struct Student {
pub name: String,
pub student_id: String,
#[serde(skip)]
pub birth_year: u32,
pub age: u32,
}
配置后,输出的json对象为:
json
{
"name": "tom",
"studentId": "J19990",
"age": 123
}
其中birth_year字段被跳过。最常见的两种使用场景是那些Option字段和空vector。
- Option
假设员工结构有化名alias_name: Option字段。如果我们想在员工没有这个字段的情况下跳过这个字段,我们可以这样做。
#[serde(skip_serializing_if="Option::is_none")]
pub alias_name: Option<String>
输出json,分为两种情形:
json
// without alias name
{
"name": "tom",
"studentId": "J19990",
}
// with alias name
{
"name": "tom",
"studentId": "J19990",
"aliasName": "middle"
}
- vector字段
例如,我们为employee结构体提供了projects: Vec字段。由于员工不是必须负责项目,它可以是一个空向量。
要跳过对空向量的序列化,可以向字段添加以下属性。
rust
#[serde(skip_serializing_if="Vec::is_empty")]
pub projects: Vec<String>,
是否配置忽略字段属性的输出差异,根据请求主体的需求,我们可以在两者之间进行选择。
json
// 未配置属性skip_serializing_if
{
"name": "tom",
"empId": "J19990",
"projects": []
}
// 增加skip_serializing_if属性配置
{
"name": "tom",
"empId": "J19990"
}
扁平化
当你想结构体的一些字段公开和/或给它们默认值,..Default::default()
方式特别有用的。下面示例创建名为SideInfo的新结构体,并将Student结构体更改为以下内容。
rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Student {
pub name: String,
pub student_id: String,
#[serde(skip_serializing_if="Option::is_none")]
pub side_info: Option<SideInfo>
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
struct SideInfo {
#[serde(skip_serializing_if="Option::is_none")]
pub pets: Option<Vec<String>>,
#[serde(skip_serializing_if="Option::is_none")]
pub address: Option<String>,
}
为了让SideInfo中address字段有值,其他字段未默认值。可以采用..Default::default()
方式创建对象:
rust
let student = Student{
name:"dan".to_owned(),
student_id: "1".to_owned(),
side_info:Some(
SideInfo{
address:Some("47 street".to_owned()),
..Default::default()
}
)
};
输出json如下:
json
{
"name": "dan",
"studentId": "1",
"sideInfo": {
"address": "47 street"
}
}
从输出看到,地址字段嵌套在sideInfo中。但是,通过将属性flatten添加到Student结构中的sideInfo字段:
rust
#[serde(skip_serializing_if="Option::is_none", flatten)]
pub side_info: Option<SideInfo>
输出json:
json
{
"name": "dan",
"studentId": "1",
"address": "47 street"
}
反序列化
除了序列化之外,Serde还简化了将JSON字符串反序列化回Rust结构的过程。反序列化允许我们解析JSON数据并将其转换为有意义的Rust对象。
使用我们的Person结构的例子,我们可以有这样的JSON表示,并通过serde_json::from_str方法进行反序列化:
rust
let json_person = r#"
{
"name": "Jane Smith",
"age": 28,
"email": "jane@example.com"
}
"#;
let person: Person = serde_json::from_str(json_person).unwrap();
println!("Name: {}", person.name);
println!("Age: {}", person.age);
println!("Email: {}", person.email);
输出结果:
json
Name: Jane Smith
Age: 28
Email: jane@example.com
错误处理
在大多数现实场景中,您将处理通常不可预测或不正确的数据。Serde允许在反序列化过程中进行错误处理:
rust
#[derive(Serialize, Deserialize, Debug)]
struct User {
name: String,
age: u8,
email: String,
}
fn main() {
let data = r#"
{
"name": "John Doe",
"age": "twenty",
"email": "john@example.com"
}"#;
let result: Result<User, _> = serde_json::from_str(data);
match result {
Ok(_) => println!("Successfully deserialized data"),
Err(err) => {
println!("We ran into an error: {}", err);
match err.classify() {
serde_json::error::Category::Io => println!("Problem reading file"),
serde_json::error::Category::Syntax => println!("Problem with JSON syntax"),
serde_json::error::Category::Data => println!("Problem with data"),
serde_json::error::Category::Eof => println!("Unexpected end of file"),
}
}
}
}
在上面的示例中,age字段不正确(它需要数字,但我们输入了字符串)。在Error:: classifier函数的帮助下,可以对错误进行分类并决定如何处理它。
常见问题
-
serde支持自定义序列化?
回答:支持,serde提供自定义序列化和反序列方式
-
serde支持那些数据格式?
回答:serde支持很多数据格式,包括:JSON, YAML, MessagePack, 和BSON等。
-
serde中**
serde_derive
**的作用?
回答:**serde_derive
**是过程宏,使用它可以自动给结构体派生Serialize和
Deserialize 实现。
-
对于大数据结构serde性能如何?
回答:它使用Rust的类型系统来减少需要运行时工作量,因此性能和效率非常高。
最后总结
本文介绍了Rust中serde包较为全面的使用指南,可以帮助你更好理解如何使用Serde实现序列化。它是一个高度通用和强大的库,是Rust程序开发中必不可少的工具包。