在 Rust 中使用 Serde 处理json

news2024/11/26 4:31:08

在 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.coloradmin.cn/o/1511800.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【机器学习300问】33、决策树是如何进行特征选择的?

还记得我在【机器学习300问】的第28问里谈到的&#xff0c;看决策树的定义不就是if-else语句吗怎么被称为机器学习模型&#xff1f;其中最重要的两点就是决策树算法要能够自己回答下面两问题&#xff1a; 该选哪些特征 特征选择该选哪个阈值 阈值确定 今天这篇文章承接上文&…

因为manifest.json文件引起的 android-chrome-192x192.png 404 (Not Found)

H5项目打包之后&#xff0c;总是有这个报错&#xff0c;有时候还有别的icon也找不见 一通调查之后&#xff0c;发现是因为引入了一个vue插件 这个插件引入之后&#xff0c;webpack打包的时候就会自动在dist文件夹中产生一个manifest.json文件这个文件里面主要就是一些icon地址的…

Hadoop学习3:问题解决

文章目录 问题解决1. ERROR: but there is no HDFS_NAMENODE_USER defined2. JAVA_HOME is not set and could not be found.3. Hadoop-DFS页面访问不了4. namenode格式化失败&#xff0c;或者dfs页面打开失败5. ERROR: but there is no YARN_RESOURCEMANAGER_USER defined. Ab…

【数据挖掘】实验1:R入门(内含详细R和RStudio安装教程)

实验1&#xff1a;R入门 一&#xff1a;实验目的与要求 1&#xff1a;根据上课PPT内容&#xff0c;掌握课堂知识并进行代码练习操作&#xff0c;提供练习过程和结果。 2&#xff1a;可COPY代码运行结果直接提交&#xff0c;如涉及到输出图等可截图。 二&#xff1a;实验内容 …

3D Gaussian Splatting for Real-Time Radiance Field Rendering(慢慢啃,还是挺复杂的)

三个关键要素 从相机配准的过程中得到的稀疏点云开始&#xff0c;使用3D Gaussian表示场景; 3D Gaussian: 是连续体积辐射场能够防止不必要的空空间优化。对 3D Gaussion进行交叉优化和密度控制: 优化各向异性血方差对场景精确表示。使用快速可视感知渲染算法来进行快速的训练…

InnoDB和MyISAM存储引擎

InnoDB mysql默认存储引擎 支持事务&#xff0c;行级锁&#xff08;并发量大&#xff09;&#xff0c;外键约束&#xff0c;容量大&#xff0c;支持缓存&#xff0c;支撑主键自增&#xff0c; 全文检索&#xff0c;不存储表的总行数&#xff0c;需要sql逐行统计 MyISAM 不…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:RemoteWindow)

远程控制窗口组件&#xff0c;可以通过此组件控制应用窗口&#xff0c;提供启动退出过程中控件动画和应用窗口联动动画的能力。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 该组件为系统接口。…

k8s+zabbix

一&#xff0c;环境&#xff1a; 1&#xff09;&#xff0c;k8s部署&#xff0c;master和node节点都部署成功 二&#xff0c;部署&#xff1a; 1&#xff09;&#xff0c;安装python3&#xff08;资源中有&#xff09; wget https://www.python.org/ftp/python/3.7.4/Python-…

【Mysql】事务与索引

目录 MySQL事务 事务的特性 并发事务的问题&#xff1f; 事务隔离级别&#xff1f; MySQL索引 数据结构 索引类型 聚簇索引与非聚簇索引 聚集索引的优点 聚集索引的缺点 非聚集索引的优点 非聚集索引的缺点 非聚集索引一定回表查询吗(覆盖索引)? 覆盖索引 联合索…

传输层协议介绍(tcp,udp),可靠性和不可靠性

目录 传输层协议 介绍 tcp协议 介绍 面向连接 可靠性 面向字节流 udp协议 介绍 无连接 不可靠 面向数据报 可靠和不可靠 可靠 不可靠 传输层协议 介绍 传输层是计算机网络体系结构中的第四层&#xff0c;它负责在网络中的不同主机之间提供端到端的数据传输 传输…

Web 常用的 扩展开发框架

当谈到提升浏览器功能和用户体验时&#xff0c;浏览器扩展成了一股强大的力量&#xff0c;备受用户青睐。在众多的Web扩展开发框架中&#xff0c;WXT和Plasmo凭借其丰富的工具和特性&#xff0c;以及简化的开发流程&#xff0c;成为开发者们的首选。在本文中&#xff0c;我们将…

基于 Jenkins 搭建一套 CI/CD 系统

一、CI/CD环境介绍 本次要实现如下效果&#xff0c;开发人员完成功能开发并提交代码到gitlab仓库&#xff0c;jenkins自动完成拉取代码、编译构建、代码扫描&#xff08;sonarqube&#xff09;、打包&#xff0c;再自动化完成部署到Tomcat服务器提供访问。 环境准备三台Centos…

jupyter 修改文件保存位置 步骤

一、找到配置文件位置 打开Anaconda Prompt&#xff0c;输入&#xff1a; jupyter notebook --generate-config 根据得到的路径&#xff0c;以记事本方式打开配置文件 二、修改路径 在文件中输入&#xff1a; c.NotebookApp.notebook_dir E:\\deepLearning\\Jupyter_files…

离线下载的pytorch/torchvision/torchaudio

链接&#xff1a;https://download.pytorch.org/whl/torch_stable.html 下载pytorch-torchvision-torchaudio等一系列一定要版本匹配&#xff0c;并且如果是在gpu上跑的话&#xff0c;一定要都是cu版本 参考链接&#xff1a;https://blog.csdn.net/AiTanXiing/article/detail…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的输电线路设备检测系统(深度学习+UI界面+Python代码+训练数据集)

摘要&#xff1a;本篇博客详细介绍了如何运用深度学习构建一个先进的输电线路设备检测系统&#xff0c;并附上了完整的实现代码。该系统利用了最新的YOLOv8算法作为其核心&#xff0c;同时也对之前版本的YOLOv7、YOLOv6、YOLOv5进行了性能比较&#xff0c;包括但不限于mAP&…

如何解决由触发器导致 MySQL 内存溢出?

由触发器导致得 OOM 案例分析过程和解决方式。 作者&#xff1a;龚唐杰&#xff0c;爱可生 DBA 团队成员&#xff0c;主要负责 MySQL 技术支持&#xff0c;擅长 MySQL、PG、国产数据库。 爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编…

为什么要有包装类?

1、典型回答 在 Java 中&#xff0c;所有的基本类型都会对应一个包装类&#xff0c;如下所示&#xff1a; 之所以要有包装类型的主要原因有以下几个&#xff1a; 面向对象要求&#xff1a;Java 是一门面向对象的编程语言&#xff0c;要求所有的数据都应该是对象。但是&#x…

【递归搜索回溯专栏】专题二:二叉树中的深搜----二叉搜索树中第K小的元素

本专栏内容为&#xff1a;递归&#xff0c;搜索与回溯算法专栏。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;递归搜索回溯专栏 &#x1f69a;代码仓库&#xff1a;小小unicorn的代…

数据库三大范式设计原则

数据库三大范式 第一范式(确保每列保持原子性) 第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值&#xff0c;就说明该数据库表满足了第一范式。 第二范式(确保表中的每列都和主键相关) 第二范式在第一范式的基础之上更进一层。第二范式需要确保数据…

基于ARMA-GARCH模型探究股价的日历效应和节假日效应【思路+代码】

目录 1. 模型定义1.1 ARMA-GARCH模型1.2 引入节假日效应的虚拟变量的新模型1.3 引入日历效应的虚拟变量的新模型 2. 实证部分2.1 准备工作2.2 引入节假日效应虚拟变量的模型建立和结果分析2.3 引入节假日效应和日历效应的虚拟变量的模型建立和结果分析 3. 结语 本文介绍了ARMA-…