在 Rust 中使用 Serde 处理json

在 Rust 中使用 Serde 处理json

在这里插入图片描述

在本文中,我们将讨论 Serde、如何在 Rust 应用程序中使用它以及一些更高级的提示和技巧。

什么是serde?

Rust中的serde crate用于高效地序列化和反序列化多种格式的数据。它通过提供两个可以使用的traits来实现这一点,这两个traits为 DeserializeSerialize 。作为生态系统中最著名的 crate 之一,它目前支持 20 多种类型的序列化(反序列化)。

首先,您需要将 crate 安装到您的 Rust 应用程序中:

cargo add serde

使用serde

Deserializing and Serializing 数据

序列化和反序列化数据的简单方法是添加 serde derive 功能。这会添加一个宏,您可以使用它来自动实现 DeserializeSerialize traits - 您可以使用 --features 标志(短的 -F 来实现):

cargo add serde -F derive

然后我们可以将宏添加到我们想要实现 DeserializeSerialize 的任何结构体或枚举中:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct MyStruct {
    message: String,
    // ... the rest of your fields
}

这允许我们使用任何支持 serde 的crate 在所述格式之间进行转换。作为示例,让我们使用 serde-json 与 JSON 格式相互转换:

use serde_json::json;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct MyStruct {
    message: String,
}

fn to_and_from_json() {
    let json = json!({"message": "Hello world!"});
    let my_struct: MyStruct = serde_json::from_str(&json).unwrap();

    assert_eq!(my_struct, MyStruct { message: "Hello world!".to_string());

    assert!(serde_json::to_string(my_struct).is_ok());
}

如果您有兴趣在 Rust 应用程序中使用 serde-json ,我们有一篇讨论 JSON 解析库的文章,您可以在此处查看。

我们还可以对许多源进行反序列化和序列化,包括文件流 I/O、JSON 字节数组等。

自定义实现反序列化和序列化

为了更好地理解 serde 在底层是如何工作的,我们还可以自定义实现 DeserializeSerialize 。这相当复杂,但现在我们将实现一个简单的。下面是序列化 i32 基元类型的简单实现:

use serde::{Serializer, Serialize};

impl Serialize for i32 {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_i32(*self)
    }
}

为了能够转换类型, serde 内部要求我们使用实现 Serializer 的类型。要为不是原生(primitive)类型 实现 Serialize ,我们可以通过序列化为原生(primitive)类型来扩展它,然后从原生(primitive)类型转换为我们想要的任何类型。如果我们想要对结构进行自定义序列化,我们也可以使用 SerializeStruct trait来执行相同的操作:

use serde::ser::{Serialize, Serializer, SerializeStruct};

struct Color {
    r: u8,
    g: u8,
    b: u8,
}

impl Serialize for Color {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // 3 is the number of fields in the struct.
        let mut state = serializer.serialize_struct("Color", 3)?;
        state.serialize_field("r", &self.r)?;
        state.serialize_field("g", &self.g)?;
        state.serialize_field("b", &self.b)?;
        state.end()
    }
}

注意,要序列化字段,字段类型还需要实现 Serialize 。如果有未实现 Serialize 的自定义类型,则需要实现 Serialize 或使用 Serialize derive宏(如果结构体/枚举 类型 包含所有实现 Serialize 的类型)。

The Deserialize trait is a little bit different and is a fair bit more complicated to implement. To be able to deserialize to a type, the type itself needs to implement Sized which means that there are a number of types which can’t use this trait (for example &str) because they are unsized types. To deserialize a type, you also need to use a type that implements the Visitor trait.
Deserialize trait 有点不同,并且实现起来要复杂一些。为了能够反序列化为类型,类型本身需要实现 Sized 这意味着有许多类型不能使用此特征(例如 &str ),因为它们是unsized 类型。要反序列化类型,您还需要使类型实现 Visitor trait。

Visitor trait使用 Rust 中的 Visitor 设计模式。这意味着它封装了一种对相同大小的对象集合进行操作的算法。它允许您编写多种不同的算法来操作数据,而无需更改任何原始功能。您可以在这里了解更多相关信息。

下面是一个 MessageVisitor 类型的示例,该类型尝试将多种类型反序列化为 String:

use std::fmt;

use serde::de::{self, Visitor};

struct MessageVisitor;

impl<'de> Visitor<'de> for MessageVisitor {
    type Value = String;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("A message that can either be deserialized from an i32 or String")
    }

    fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(value)
    }

    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(value.to_owned())
    }

    fn visit_i32<E>(self, value: i32) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(value.to_string())
    }
}

正如您所看到的,实现的代码量相当大!然而,它也使我们能够使实现变得更加简单。通过实现 Visitor 特征,可以将实现它的类型传递给 Deserialize 方法,然后将 JSON 反序列化到我们的结构中:

use serde::{Deserialize, Deserializer};

impl<'de> Deserialize<'de> for MyStruct {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        // note: don't use unwrap in production!
        let message = deserializer.deserialize_string(MessageVisitor).unwrap();
        Ok(Self { message })
    }
}

您还可以在此处找到有关反序列化结构的文档。但是,一般来说,建议您使用 derive 功能宏,因为手动实现(如前面所示)代码量相当大。该实现主要涉及使用访问者来访问映射或序列,然后迭代元素以将其反序列化。

使用 serde 属性

当涉及到 serde 时,crate 还具有许多有用的属性宏,我们可以在类型上使用它们,以允许在反序列化字段或序列化为结构时进行字段重命名等操作。最好的例子之一是当您与用某种语言编写的 API 进行交互时,该语言的键可能是 Rust 中的保留关键字。您可以添加 #[serde(rename)] 属性宏,如下所示:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
pub struct MyStruct {
    #[serde(rename = "type")]
    kind: String
}

这可以让您解决名称冲突的问题!

您还可以使用 rename_all 属性将所有字段重命名为另一个大小写:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MyStruct {
    my_message: String
}

现在,当您序列化此结构时, my_message 应该自动变成 myMessage !非常适合使用以其他语言或不同约定编写的 API。

如果您不想将字段包装在 Option 中,您还可以使用 #[serde(default)] 实现默认值。这只是允许用默认值填充字段,而不是 报错。您还可以使用 #[serde(default = "path")] 来指向提供自动默认值的函数。例如,这个结构体和函数:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
pub struct MyStruct {
    #[serde(path = "my_function")]
    my_message: String,
}

fn my_function() -> String {
    "Hello world!".to_string()
}

serde 还提供其他有用的属性,例如能够在结构顶部使用 #[serde(deny_unknown_fields)] 拒绝未知字段。这使您可以确保序列化和反序列化时结构完全按原样。

Deserializing and Serializing enums

让我们看一下这个枚举类型:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
enum MyEnum {
    Data { id: String, data: Value },
    SomeOtherData { id: i32, name: String }
}

请注意,在与此枚举进行转换时,可以采用两个选项:

  • 名为 id 的字符串字段和 data 这是一个 JSON 值(可以是map、值或 Json 值可以保存的任何内容)
  • 名为 idi32 字段和名为 nameString 字段

然后,您可以匹配枚举变量以进行进一步处理。

当第一个枚举变体用 JSON 编写时,您可以看到它应该与此对应:

{
    "Data": {
        "id": "your_id_here",
        "data": { .. }
    }
}

这种类型的数据是“外部标记的”——这意味着数据的特征是标识符位于 JSON 对象的外部。我们可以添加内联标记,以便标识符位于crate的内部 - 让我们看看它会是什么样子:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
#[serde(tag = "type")]
enum MyEnum {
    Data { id: String, data: Value },
    SomeOtherData { id: i32, name: String }
}

现在 JSON 表示如下所示:

{
    "type": "Data",
    "id": "your_id_here",
    "data": { .. }
}

有兴趣内容吗? serde 文档有一个关于tag 的页面,您可以在此处Enum representations · Serde找到。

Crates that work well with Serde

serde_with

serde_with 是一个提供自定义反/序列化 帮助程序的包,可与 serdewith 注释一起使用。通常,您可以定义一个模块供(反)序列化器使用,该模块位于用于自定义(反)序列化的自定义模块之后:

#[derive(Deserialize, Serialize)]
pub struct MyStruct {
    #[serde(with = "my_module")]
    my_message: String
}

使用 serde_with 时,它的工作原理是用名为 serde_as 的新注释替换 with 注释。使用这个新的属性宏,您可以做很多事情:

  • 使用 DisplayFromStr traits反序列化类型。
  • 支持大于 32 个元素的数组。
  • 跳过序列化空选项类型。
  • 将逗号分隔的列表反序列化为 Vec<String>

要使用 serde_with ,您需要手动或使用以下命令将其添加到 Cargo.toml 中:

cargo add serde_with

然后您需要将 serde_as 添加到您想要使用它的类型,如下所示:

use serde_with::{serde_as, DisplayFromStr};
#[serde_as]
#[derive(Deserialize, Serialize)]
struct MyStruct {
    // Serialize with Display, deserialize with FromStr
    #[serde_as(as = "DisplayFromStr")]
    my_number: u8,
}

该结构允许您与字符串相互转换,但 Rust 结构中的类型本身为 u8 !非常有用,对吧?

这个crate还附带了一个指南,您可以使用它来充分利用 serde_with 。总的来说,这是 serde 的一个强大的伴侣crate。

serde_bytes

serde_bytes 是一个允许优化处理 &[u8]Vec<u8> 类型的包 - 而 serde 本身能够处理这些类型,某些格式可以更有效地反/序列化。使用起来非常简单 - 您只需将其添加到 Cargo.toml 中,然后通过 #[serde(with = "serde_bytes")] 注释添加它,如下所示:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct MyStruct {
    #[serde(with = "serde_bytes")]
    byte_buf: Vec<u8>,
}

总的来说,这是一个易于使用且简单的 crate,无需太多知识即可提高性能。

尾声

我希望您喜欢阅读有关 Serde 的文章!它是一个非常强大的 Rust 包,构成了大多数 Rust 应用程序的基础。


Using Serde in Rust

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/452771.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数字孪生+工业互联网标识解析,打造智能工厂新标杆!

当前&#xff0c;工业4.0浪潮愈发澎湃&#xff0c;加快数字化、网络化、智能化发展成为了制造业转型升级的必然要求。 51WORLD基于数字孪生技术与工业互联网标识解析体系&#xff0c;打造了一个集协同化供应、个性化定制、智能化生产于一体的全连接产线孪生平台&#xff08;以…

AndroidStudio设计登录页源码(音悦app)

目录 一、代码 二、效果 一、代码 1.在activity_main.xml里的代码 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent&quo…

(一)RabbitMQ实战——rabbitmq的核心组件及其工作原理介绍

前言 RabbitMQ是一个开源的消息代理软件&#xff0c;它实现了高级消息队列协议&#xff08;AMQP&#xff09;标准&#xff0c;提供可靠的消息传递机制。RabbitMQ可以用于在应用程序之间传递消息&#xff0c;实现不同应用系统之间的解耦和通信。它支持多种编程语言&#xff0c;…

11.Java---语法总结之一个小项目

图书管理系统 Java学习了很久了,今天将运用之前学习的所有东西整理做个小小的小项目. 1.首先是各种包和操作方法建好 2.然后是项目的大框架搭好 3.然后就开始实现各个部分了 看看最后的运行结果吧! 管理员测试 1.登录&显示图书的运行结果 2.查找&新增图书的运行结…

C goto 语句

C 语言中的 goto 语句允许把控制无条件转移到同一函数内的被标记的语句。 注意&#xff1a;在任何编程语言中&#xff0c;都不建议使用 goto 语句。因为它使得程序的控制流难以跟踪&#xff0c;使程序难以理解和难以修改。任何使用 goto 语句的程序可以改写成不需要使用 goto 语…

安卓逆向 安装frida

1,准备工作 安装adb环境,python最高支持到3.8 2,下载雷电模拟器5.0(安卓是7.0)要64位的 雷电安卓模拟器-手游模拟器安卓版_android手机模拟器电脑版_雷电模拟器官网 3,下载frida服务端(版本要与安卓版本匹配;与客户端版本要一致) 在模拟器查看架构 adb shell getprop ro.pr…

数组和指针笔试题目解析---掌握他们拿下指针魔鬼

目录 一、前言二、一维数组2.1代码2.2分析2.3验证运行结果 三、字符数组3.1代码13.1.1分析3.1.2验证运行结果 3.2代码23.2.1分析3.2.2验证运行结果 3.3代码33.3.1分析3.3.2验证运行结果 3.4代码43.4.1分析3.4.2验证运行结果 3.5代码53.5.1分析3.5.2验证运行结果 3.6代码63.6.1分…

D7805 ——体积小,成本低,性能好

D7805 构成的 5V 稳压电源为输出电压5V&#xff0c;输出电流 1000mA 的稳压电源它由滤波电容 C1,C3,防止自激电容 C2、C3 和一只固定三端稳压器&#xff08;7805&#xff09;后级加 LC 滤波极为简洁方便地搭成&#xff0c;输入直流电压范围为 7~35V&#xff0c;此直流电压经过D…

机械女生,双非本985硕,目前学了C 基础知识,转嵌入式还是java更好?

作为单片机项目开发的卖课佬&#xff0c;个人建议&#xff0c;先转嵌入式单片机开发方向&#xff0c;哈哈。 java我也学过&#xff0c;还学过oracle、mysql数据库&#xff0c;只是当时没做笔记&#xff0c;找不好充分的装逼证据了。 从实习通过业余时间&#xff0c;学到快正式毕…

(黑马出品_高级篇_03)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式

&#xff08;黑马出品_高级篇_03&#xff09;SpringCloudRabbitMQDockerRedis搜索分布式 微服务技术——多级缓存 今日目标1.什么是多级缓存2.JVM进程缓存2.1.导入案例2.1.1.安装MySQL2.1.1.1.准备目录2.1.1.2.运行命令2.1.1.3.修改配置 2.1.1.4.…

学编程前需要知道哪些编程语言呢?

编程语言的选择对学习编程的重要性不可低估。不同编程语言各有独特特点和适用场景&#xff0c;合理选择适合自身需求的编程语言能事半功倍。初学者而言&#xff0c;选择一门简洁易读的编程语言如Python&#xff0c;可以快速掌握编程基础原理&#xff0c;轻松编写程序&#xff0…

202003 青少年软件编程(Scratch)等级考试试卷(二级)

第1题:【 单选题】 在以下积木中,( )可以将声音的音量增大 A: B: C: D: 【正确答案】: B 【试题解析】 : 第2题:【 单选题】 如下图所示脚本运行的结果是( ) A:画一条直线 B:画一个三角形 C:画一个圆形 D:画一条虚线 【正确答案】: D 【试题解析】 : 第3…

【新书推荐】26.3 模块间的信息传递

模块间的信息传递主要表现为模块间过程调用时的参数传递。在第十一章介绍的过程调用参数传递原则和方法依然有效。少量的参数可以利用寄存器传递或堆栈传递&#xff0c;大量的参数可先组织在一个缓冲区中&#xff0c;然后利用寄存器或堆栈传递相应的指针。 如果利用约定的存储…

ES分片均衡策略分析与改进

从故障说起 某日早高峰收到 Elasticsearch 大量查询超时告警&#xff0c;不同于以往&#xff0c;查看 Elasticsearch 查询队列监控后发现&#xff0c;仅123节点存在大量查询请求堆积。 各节点查询队列堆积情况 查看节点监控发现&#xff0c;123节点的 IO 占用远高于其他节点。…

CcFrame Logo完成

Compact celerity framework CCFrame&#xff1a;一个快速开发、精简压缩的SAAS微服务后端架构 仅需要一台阿里云2核2G机器即可完整运行平台系统及其所有的运行及数据支持 微服务、远程-本地二级缓存架构、ES索引、队列、分库分表、集群定时器&#xff0c;一个都不能少 谁说2…

基于YOLOv8深度学习的木薯病害智能诊断与防治系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

【R语言实战】——fGARCH包在金融时序上的模拟应用

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

【惠友小课堂】健康开学第一课,青少年脊柱侧弯了解一下?让孩子少走“弯”路

寒假结束迎来开学&#xff0c;家长们又开始了新一轮的“内卷”之旅&#xff0c;可在关心学业的同时&#xff0c;千万不能忽略孩子们的身体健康&#xff0c;脊柱侧弯已成为继肥胖症、近视之后我国儿童青少年的第三大健康“杀手”。 至于引起脊柱侧弯的原因&#xff0c;可能很多家…

安装Android Studio遇到Unable to access Android SDK add-on list的错误

第一次安装android studio的时候&#xff0c;提示&#xff1a;unable to access Android sdk add-on list 解决办法 这个错误一般是android studoi代理没有设置导致的&#xff0c;需要在setting里面设置&#xff1a; 点击Android Studio - Preferences&#xff0c;在 Appeara…

【Java设计模式】十八、观察者模式

文章目录 1、观察者模式2、案例&#xff1a;模拟微信公众号3、总结4、在源码中的实际应用 1、观察者模式 发布订阅模式&#xff0c;一对多&#xff0c;让多个观察者对象同时监听某一个主题对象。主题对象状态发生变化时&#xff0c;通知所有的观察者对象&#xff0c;让它们自动…
最新文章