Rust中解析JSON的4种方法

在本文中,我们将讨论如何在 Rust 中使用 JSON 解析库,以及最流行的库的比较及其性能

1、手动解析 JSON
要开始在 Rust 中使用 JSON,您需要安装一个可让您轻松操作 JSON 的库。目前可用的流行板条箱之一是serde-json. 您可以通过运行以下命令来安装它:
cargo add serde-json

完成后,您可以像这样手动创建 JSON:

use serde_json::{Result, Value};

fn untyped_example() -> Result<()> {
    // Some JSON input data as a &str. Maybe this comes from the user.
    let data = r#
"
        {
           
"name": "John Doe",
           
"age": 43,
           
"phones": [
               
"+44 1234567",
               
"+44 2345678"
            ]
        }
"#;

   
// 将数据字符串解析为 serde_json::Value。
    let v: Value = serde_json::from_str(data)?;

   
// 通过方括号索引访问部分数据。
    println!(
"Please call {} at the number {}", v["name"], v["phones"][0]);

    Ok(())
}

然而,我们可以做得更好。例如,我们可以将 JSON 与结构序列化,这有很多应用。我们可以在 JSON 模板、Web 服务、CLI 参数等中使用它。让我们在下一节中看看这一点。

2、使用 Serde 解析 JSON
Serde 是一个包,可以帮助您将数据序列化和反序列化为各种格式,其中一个流行的用途是用于 JSON。如果您使用 Rust 编写 Web 服务,Serde 是您的朋友,因为您将经常处理可能需要发送或接收的 JSON 数据。Serde 提供了两个主要特征来帮助您实现此目的:Serialize和Deserialize。为了方便起见,添加了派生宏实现来帮助解决此问题。请参阅下文了解如何执行此操作:

use serde::{Deserialize, Serialize};

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

fn convert_json_to_struct() {
    // create a raw JSON string from the json! macro and turn it into a MyStruct struct
    let raw_json_string = json!({
"message": "Hello world!"});
    let my_struct: MyStruct = serde_json::from_str(raw_json_string).unwrap();
}

Serialize您还可以通过添加一个实现and的结构作为另一个也实现andDeserialize的结构的字段来创建嵌套 JSON :SerializeDeserialize

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct Post {
      nested_json: PostMetadata,
    title: String,
      body: String
}

#[derive(Serialize, Deserialize)]
pub struct PostMetadata {
      timestamp_created: DateTime<Utc>,
    timestamp_last_updated: Datetime<Utc>,
      categories: Vec<String>,
}

一种用例是将 JSON 嵌套在 Web 服务中。例如,当您收到具有 JSON 主体的 API 的 POST 请求时,您通常会将相关Json类型作为处理函数参数传递。见下文:

use axum::Json;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct Post {
      nested_json: PostMetadata,
    title: String,
      body: String
}

#[derive(Serialize, Deserialize)]
pub struct PostMetadata {
      timestamp_created: DateTime<Utc>,
    timestamp_last_updated: Datetime<Utc>,
      categories: Vec<String>,
}

async fn receive_some_json(
  // this extractor consumes a JSON body and converts it into the struct type given
    Json(json): Json<Post>
) -> Json<Post> {
  println!(
"{:?}", json);
    Json(json)
}

除了前面显示如何serde_json从 JSON 字符串转换为结构体的代码片段之外,您还可以从其字节表示形式转换为结构体:

let json_as_bytes = b"
        {
            \"message\": \"Hello world!\",
        }
";

    let my_struct: MyStruct = serde_json::from_slice(json_as_bytes).unwrap();

如果您想将结构作为字节数组存储在某处,然后稍后将其转回结构,那么这特别有用!

同样,您也可以使用该方法从 JSON 的 IO 流读取 JSON 并将其转换为结构体.from_reader()。下面是一个取自serde_json文档的示例,说明如何将它与 TCP 流一起使用:

use serde::Deserialize;
use std::error::Error;
use std::net::{TcpListener, TcpStream};

#[derive(Deserialize, Debug)]
struct User {
    fingerprint: String,
    location: String,
}

fn read_user_from_stream(tcp_stream: TcpStream) -> Result<User, Box<dyn Error>> {
    let mut to_be_deserialized = serde_json::Deserializer::from_reader(tcp_stream);
    let user = User::deserialize(&mut to_be_deserialized)?;

    Ok(user)
}

fn main() {
    let listener = TcpListener::bind("127.0.0.1:4000").unwrap();

    for stream in listener.incoming() {
        println!(
"{:#?}", read_user_from_stream(stream.unwrap()));
    }
}

通过这种方式,您可以直接从流中反序列化,而不是在内存中添加缓冲。如果您收到大量基于 JSON 的数据,这可以为您提供很大帮助!

尽管serde-json可能是最受欢迎的板条箱,但它绝不是最快的。与此同时,还出现了一些其他 crate,以提高一般 JSON 解析性能。然而,作为性能的交换,CPU SIMD 扩展要求有一些注意事项。不安全代码的使用也有所增加,尽管一般来说,我们已尽最大努力确保代码可以安全使用。
所有这些 crate 大部分都具有相同的 API。除非另有说明,否则您可以安全地在这些库之间切换,并期望每个库中使用大致相同的 JSON 接口。

3、serde-json
serde-json 是最容易使用的 Rust JSON 库。serde-json 还支持 no_std,允许你关闭默认的 std 功能并启用 alloc。

就性能而言,serde-json 本身并不慢。不过,它比本列表中的其他 JSON 库要慢一些。这主要是因为它针对非并行 CPU 使用进行了优化。特别是如果你能使用现代的 x86 CPU,你可能想继续阅读,了解更多性能更好的选择。不过,这个 crate 也是 Rust 社区中最常用、最受支持的,所以如果你在使用过程中遇到问题,很容易找到帮助!

3、simd-json
simd-json 是 Simdjson C++ JSON 分析器的 Rust 移植版本,内置 serde 兼容性。正如其名称所示,该库使用 SIMD(单指令多数据的缩写)。这是一种用于并行处理多个数据点的技术,可大大提高处理速度!不过需要注意的是,它要求你的系统具有 x86 处理能力,而且在运行期间,它会选择性能最佳的 SIMD 功能集。如果没有可用的特征集,也有一个未经优化的 Rust 实现,但在文档中提到不应依赖它。

文档中提到,simd-json 可以在本地目标编译时充分使用。你可以在运行程序时,在 rustc 中启用以下编译器选项来实现这一点:

rustc -C target-cpu=native

不过,如果你和大多数使用 Cargo 的人一样,可能想使用 cargo run。在示例中,你可以在 .cargo/config 中创建一个配置,然后添加以下内容:

[build]
rustflags = ["-C", "target-cpu=native"]

一般来说,虽然该库的运行速度相当快,但需要注意的是,由于该板块是 C++ 板块的移植,因此存在大量不安全代码。这并不是说你不应该使用它,而是要谨慎使用(正如该板块所说)。尽管如此,还是有一节关于安全性的内容,详细介绍了如何坚持最佳实践(如单元测试),以确保尽可能安全地使用该板块。

还应提及的是,为了获得最佳性能,通常最好启用 jemalloc 或 mimalloc 功能,以便充分利用该库。

一般来说,simd-json 的应用程序接口与 serde-json 相同,因此如果你想在任何时候切换,一般都不会有任何问题。

4、sonic-rs
sonic-rs 是一个具有 SIMD 功能的 JSON 操作 Rust 实现。该库在 C++ 和 Go 中也有对应的库!虽然它曾经需要 Rust nightly 工具链,但现在它支持稳定的 Rust。与 simd-json 类似,它也需要 x86 CPU 架构才能充分发挥作用。

与 simd-json 一样,要使用 sonic-rs,需要在运行程序时启用 rustc 中的以下编译器选项:
rustc -C target-cpu=native

您可以在 .cargo/config 中创建配置,然后添加以下内容,以便在使用 cargo run 时启用它:

[build]
rustflags = ["-C", "target-cpu=native"]


这样,您就可以为 SIMD 构建系统,而无需做其他任何事情!

与 simd-json 一样,它也使用了相当数量的不安全代码。不过,如果你在库中搜索不安全代码,你会发现不安全代码可能比之前的库还要多。至于不安全保证是如何实现的,也没有太多的文档说明,因此,尽管该库可能比 simd-json 更快,但你还是要仔细检查是否存在未定义的行为!

sonic-rs 还提供了一些额外的方法,用于延迟评估和提高速度。例如,如果你想要一个 JSON 字符串字面量,可以在反序列化时使用 LazyValue 类型将其转换为仍带有正斜杠的 JSON 字符串值。如果您不担心不安全行为或确信不会出错,还可以使用许多未选中的方法。

虽然 sonic-rs 是一个相当快的库,但它也是一个较新的板块,因此板块中缺少了一些方法,如 from_reader(允许从 IO 流中读取)。这个问题已在 GitHub 上提出,希望能尽早实现。

基准
你可以在这里here.找到 simd-json 和 serde-json 的基准测试。与 serde-json 相比,simd-json 有相当大的改进。

你可以在这里here,找到 sonic-rs 的基准测试,它也与 simd-json 和 serde-json 进行了比较。正如你所看到的,最终结果的格式与 simd-json 和 serde-json 基准不同,因此从每秒处理的数据来看,要理解它更困难一些。不过,在大多数情况下,sonic-rs 比 simd-json 和 serde-json 都快得多(有时甚至快得多!)。