快速学习Serde包实现rust对象序列化

在处理HTTP请求时,我们总是需要在数据结构对象(可以是enum、struct等)和序列化数据格式(例如JSON,用与存储或传输,并可以反序列化的格式)之间来回转换。

Serde是一个库(crate),用于高效、通用地序列化和反序列化Rust数据结构。在本文中,我将向您展示如何使用Attributes自定义Serde派生生成的序列化和反序列化实现。

入门示例

  • 增加依赖

    在项目目录中执行命令:

    bash 复制代码
    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。

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函数的帮助下,可以对错误进行分类并决定如何处理它。

常见问题

  1. serde支持自定义序列化?

    回答:支持,serde提供自定义序列化和反序列方式

  2. serde支持那些数据格式?

    回答:serde支持很多数据格式,包括:JSON, YAML, MessagePack, 和BSON等。

  3. serde中**serde_derive**的作用?

​ 回答:**serde_derive**是过程宏,使用它可以自动给结构体派生SerializeDeserialize 实现。

  1. 对于大数据结构serde性能如何?

    回答:它使用Rust的类型系统来减少需要运行时工作量,因此性能和效率非常高。

最后总结

本文介绍了Rust中serde包较为全面的使用指南,可以帮助你更好理解如何使用Serde实现序列化。它是一个高度通用和强大的库,是Rust程序开发中必不可少的工具包。

相关推荐
Oneforlove_twoforjob13 分钟前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
engchina29 分钟前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
向宇it29 分钟前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
诚丞成1 小时前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++
Smile灬凉城6661 小时前
反序列化为啥可以利用加号绕过php正则匹配
开发语言·php
lsx2024061 小时前
SQL MID()
开发语言
Dream_Snowar1 小时前
速通Python 第四节——函数
开发语言·python·算法
西猫雷婶1 小时前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv
鸿蒙自习室1 小时前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
言、雲2 小时前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库