在处理HTTP请求时,我们总是需要在数据结构对象(可以是enum、struct等)和序列化数据格式(例如JSON,用与存储或传输,并可以反序列化的格式)之间来回转换。
Serde是一个库(crate),用于高效、通用地序列化和反序列化Rust数据结构。在本文中,我将向您展示如何使用Attributes自定义Serde派生生成的序列化和反序列化实现。
入门示例
-
增加依赖
在项目目录中执行命令:
cargo 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。
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);
}
输出将如下所示:
{"name":"John Doe","age":30,"email":"john@example.com"
}
看起来太棒了!但是,实际应用中,生成json需要适用不同场景,如重新命名字段、忽略部分字段、拉平嵌套对象等。下面分别进行举例说明。
命名规范
例如,我们实际上希望使用studentId而不是student_id作为字段名。
- 字段重命名
使用rename对单个字段进行重命名:
struct Student {pub name: String, #[serde(rename="studentId")pub student_id: String,
}
- 命名规范
使用rename_all 实现整个struct遵循骆驼命名规则:
#[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。
struct Student {pub name: String, pub student_id: String,pub birth_year: u32,pub age: u32,
}
我们可能希望动态更新年龄,因此需要对学生的birth_year的引用。但是,当我们发送请求时,应该只显示age字段。这可以使用#[serde(skip)]来解决。
struct Student {pub name: String, pub student_id: String,#[serde(skip)]pub birth_year: u32,pub age: u32,
}
配置后,输出的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,分为两种情形:
// without alias name
{"name": "tom","studentId": "J19990",
}// with alias name
{"name": "tom","studentId": "J19990","aliasName": "middle"
}
- vector字段
例如,我们为employee结构体提供了projects: Vec字段。由于员工不是必须负责项目,它可以是一个空向量。
要跳过对空向量的序列化,可以向字段添加以下属性。
#[serde(skip_serializing_if="Vec::is_empty")]
pub projects: Vec<String>,
是否配置忽略字段属性的输出差异,根据请求主体的需求,我们可以在两者之间进行选择。
// 未配置属性skip_serializing_if
{"name": "tom","empId": "J19990","projects": []
}// 增加skip_serializing_if属性配置
{"name": "tom","empId": "J19990"
}
扁平化
当你想结构体的一些字段公开和/或给它们默认值,..Default::default()
方式特别有用的。下面示例创建名为SideInfo的新结构体,并将Student结构体更改为以下内容。
#[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()
方式创建对象:
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如下:
{"name": "dan","studentId": "1","sideInfo": {"address": "47 street"}
}
从输出看到,地址字段嵌套在sideInfo中。但是,通过将属性flatten添加到Student结构中的sideInfo字段:
#[serde(skip_serializing_if="Option::is_none", flatten)]pub side_info: Option<SideInfo>
输出json:
{"name": "dan","studentId": "1","address": "47 street"
}
反序列化
除了序列化之外,Serde还简化了将JSON字符串反序列化回Rust结构的过程。反序列化允许我们解析JSON数据并将其转换为有意义的Rust对象。
使用我们的Person结构的例子,我们可以有这样的JSON表示,并通过serde_json::from_str方法进行反序列化:
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);
输出结果:
Name: Jane Smith
Age: 28
Email: jane@example.com
错误处理
在大多数现实场景中,您将处理通常不可预测或不正确的数据。Serde允许在反序列化过程中进行错误处理:
#[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程序开发中必不可少的工具包。