#json-parser #declarative-macro #json #deserialize-json #serialization #deserialize #proc-macro

merde_json

使用jiter和声明性宏进行JSON序列化和反序列化

10个稳定版本

2.4.0 2024年8月16日
2.3.1 2024年8月16日
2.1.2 2024年7月31日
1.0.1 2024年7月29日

124 in 编码

Download history 307/week @ 2024-07-25 139/week @ 2024-08-01 9/week @ 2024-08-08 455/week @ 2024-08-15

910 monthly downloads
Used in merde_json_types

Apache-2.0 OR MIT

61KB
1.5K SLoC

license: MIT/Apache-2.0 crates.io docs.rs

merde_json

The merde_json logo: a glorious poop floating above a pair of hands

Logo by Misiasart

merde_json通过特质、声明性宏和一些纪律,涵盖了“90%的使用场景”的JSON操作。

它优化了编译时间短和避免复制(但不是所有分配),如果愿意放弃一些proc宏的便利,非常适合用于Web服务器。

底层的JSON解析器是jiter,它提供了一个基于事件的用户界面,当merde_json的性能不足以满足需求时,可以选择使用。

约定 + 从 serde_json 迁移

serde 允许您使用proc宏推导 SerializeDeserialize 特质

use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct MyStruct {
    name: String,
    age: u8,
}

相比之下,merde_json提供声明性宏

use merde_json::Fantome;
use std::borrow::Cow;

#[derive(Debug, PartialEq)]
struct MyStruct<'src, 'val> {
    _boo: Fantome<'src, 'val>,

    name: Cow<'val, str>,
    age: u8,
}

merde_json::derive! {
    impl(JsonSerialize, JsonDeserialize) for MyStruct {
        name,
        age
    }
}

声明性宏 = 在编译时要做的工作更少,只要遵循几条规则

  • 所有结构体恰好有两个生命周期参数:'src和'val
  • 所有结构体都有一个 _boo 字段,用于不使用生命周期参数的结构体
  • 字段名称在结构体和宏中都列出了两次(声明性宏的限制)
  • 对于所有字符串,使用 Cow<'val, str>,而不是根据情况在 &strString 之间选择

阅读 《Cows的神秘生活》 了解Rust的“写时复制”类型的简介。

反序列化

[from_str][] 是jiter API的薄包装,底层的JSON解析器。它为您提供 JsonValue,然后您可以通过 [JsonDeserialize] 特质将其解构为Rust值

# use merde_json::{Fantome, JsonDeserialize, JsonSerialize, ToRustValue};
# use std::borrow::Cow;
#
# #[derive(Debug, PartialEq)]
# struct MyStruct<'src, 'val> {
#     _boo: Fantome<'src, 'val>,
#
#     name: Cow<'val, str>,
#     age: u8,
# }
#
# merde_json::derive! {
#     impl(JsonSerialize, JsonDeserialize) for MyStruct { name, age }
# }
#
# fn main() -> Result<(), merde_json::MerdeJsonError> {
let input = String::from(r#"{"name": "John Doe", "age": 30}"#);
let value = merde_json::from_str(&input)?;
let my_struct = MyStruct::json_deserialize(Some(&value));
println!("{:?}", my_struct);
# Ok(())
# }

为了方便起见,您可以使用ToRustValue::to_rust_value

# use merde_json::{Fantome, JsonDeserialize, JsonSerialize, ToRustValue};
# use std::borrow::Cow;
#
# #[derive(Debug, PartialEq)]
# struct MyStruct<'src, 'val> {
#     _boo: Fantome<'src, 'val>,
#     name: Cow<'val, str>,
#     age: u8,
# }
#
# merde_json::derive! {
#     impl(JsonSerialize, JsonDeserialize) for MyStruct { name, age }
# }
#
# fn main() -> Result<(), merde_json::MerdeJsonError> {
let input = String::from(r#"{"name": "John Doe", "age": 30}"#);
let value = merde_json::from_str(&input)?;
// Note: you have to specify the binding's type here.
// We can't use a turbofish anymore than we can with `Into::into`.
let my_struct: MyStruct = value.to_rust_value()?;
println!("{:?}", my_struct);
# Ok(())
# }

然而,不要忽视这样一个事实,my_structvalue借用,而value又从input借用。

我们需要三个显式绑定,尽管尝试内联其中之一很诱人。这将导致编译错误“临时值在借用时已丢弃”

# use merde_json::{Fantome, JsonDeserialize, JsonSerialize, ToRustValue};
# use std::borrow::Cow;
#
# #[derive(Debug, PartialEq)]
# struct MyStruct<'src, 'val> {
#     _boo: Fantome<'src, 'val>,
#     name: Cow<'val, str>,
#     age: u8,
# }
#
# merde_json::derive! {
#     impl(JsonSerialize, JsonDeserialize) for MyStruct { name, age }
# }
#
# fn main() -> Result<(), merde_json::MerdeJsonError> {
let input = String::from(r#"{"name": "John Doe", "age": 30}"#);
let value = merde_json::from_str(&input).unwrap();
let my_struct = MyStruct::json_deserialize(Some(&merde_json::from_str(&input).unwrap()));
println!("{:?}", my_struct);
# Ok(())
# }

移动反序列化的值

如何返回一个新反序列化的值,同时处理那两个令人烦恼的生命周期?

将它们都设置为'static!然而,这失败了,因为反序列化的值不是T<'static, 'static> —— 它仍然从源代码('src)和被反序列化的JsonValue'val)借用。

此代码无法编译

# use merde_json::{Fantome, JsonDeserialize, JsonSerialize, ToRustValue};
# use std::borrow::Cow;
#
# #[derive(Debug, PartialEq)]
# struct MyStruct<'src, 'val> {
#     _boo: Fantome<'src, 'val>,
#     name: Cow<'val, str>,
#     age: u8,
# }
#
# merde_json::derive! {
#     impl(JsonSerialize, JsonDeserialize) for MyStruct { name, age }
# }
#
fn return_my_struct() -> MyStruct<'static, 'static> {
    let input = String::from(r#"{"name": "John Doe", "age": 30}"#);
    let value = merde_json::from_str(&input).unwrap();
    let my_struct: MyStruct = value.to_rust_value().unwrap();
    my_struct
}
# fn main() -> Result<(), merde_json::MerdeJsonError> {
let my_struct = return_my_struct();
println!("{:?}", my_struct);
# Ok(())
# }

...

---- src/lib.rs - (line 157) stdout ----
error[E0515]: cannot return value referencing local variable `value`
  --> src/lib.rs:177:5
   |
21 |     let my_struct: MyStruct = value.to_rust_value().unwrap();
   |                               ----- `value` is borrowed here
22 |     my_struct
   |     ^^^^^^^^^ returns a value referencing data owned by the current function

通过派生[ToStatic]特质,您可以将从MyStruct<'src, 'val>转换为MyStruct<'static, 'static>

# use merde_json::{Fantome, JsonDeserialize, JsonSerialize, ToRustValue, ToStatic};
# use std::borrow::Cow;
#
# #[derive(Debug, PartialEq)]
# struct MyStruct<'src, 'val> {
#     _boo: Fantome<'src, 'val>,
#     name: Cow<'val, str>,
#     age: u8,
# }
#
merde_json::derive! {
    //                                     👇
    impl(JsonSerialize, JsonDeserialize, ToStatic) for MyStruct { name, age }
}

fn return_my_struct() -> MyStruct<'static, 'static> {
    let input = String::from(r#"{"name": "John Doe", "age": 30}"#);
    let value = merde_json::from_str(&input).unwrap();
    let my_struct: MyStruct = value.to_rust_value().unwrap();
    my_struct.to_static()
}
# fn main() -> Result<(), merde_json::MerdeJsonError> {
let my_struct = return_my_struct();
println!("{:?}", my_struct);
# Ok(())
# }

当然,ToStatic::to_static通常涉及堆分配。如果您只是暂时处理一些JSON有效负载,可以考虑接受一个回调并传递对您值的共享引用——这比您想象的更常见!

反序列化混合类型的数组

现实世界的JSON有效负载可以有混合类型的数组。您可以将它们保持为[Vec]的[JsonValue],直到您知道如何处理它们

use merde_json::{Fantome, JsonDeserialize, JsonSerialize, ToRustValue, JsonValue, MerdeJsonError};

#[derive(Debug, PartialEq)]
struct MixedArray<'src, 'val> {
    _boo: Fantome<'src, 'val>,
    items: Vec<&'val JsonValue<'src>>,
}

merde_json::derive! { impl(JsonDeserialize) for MixedArray { items } }

fn main() -> Result<(), merde_json::MerdeJsonError> {
    let input = r#"{
        "items": [42, "two", true]
    }"#;
    let value = merde_json::from_str(input)?;
    let mixed_array: MixedArray = value.to_rust_value()?;

    println!("Mixed array: {:?}", mixed_array);

    // You can then process each item based on its type
    for (index, item) in mixed_array.items.iter().enumerate() {
        match item {
            JsonValue::Int(i) => println!("Item {} is an integer: {}", index, i),
            JsonValue::Str(s) => println!("Item {} is a string: {}", index, s),
            JsonValue::Bool(b) => println!("Item {} is a boolean: {}", index, b),
            _ => println!("Item {} is of another type", index),
        }
    }

    Ok(())
}

注意:这就是为什么我们需要两个生命周期:JsonValue<'s>相对于's是不变的。JsonValue<'val>不是JsonValue<'src>的子类型,即使当'src: 'val

这里的其他选项是将items保持为[JsonArray],甚至[JsonValue]。或者,items可以是类型为Items的,它有一个手动实现的[JsonDeserialize]。查看mixed示例以获得灵感。

从其他crate反序列化类型

您将需要使用新类型包装器:您无法在time::OffsetDateTime(一个外部crate的类型)上实现JsonSerializer(一个外部crate的类型),这是根据孤儿规则的。

但是您可以在YourType<time::OffsetDateTime>上实现它——这对于日期时间类型特别有效,因为我喜欢RFC3339,但您可能想要做其他事情。

merde_json_types crate的目标是收集这样的包装器类型:它应该无条件地拉取,并且有一个merde_json功能,它有条件地实现包装器类型的相关特质,这使得在没有使用merde_json的情况下使用您的crate成为一种低成本的选择。

序列化

序列化通常看起来像

# use merde_json::{Fantome, JsonSerialize, JsonDeserialize, ToRustValue};
# use std::borrow::Cow;
#
# #[derive(Debug, PartialEq)]
# struct MyStruct<'src, 'val> {
#     _boo: Fantome<'src, 'val>,
#     name: Cow<'val, str>,
#     age: u8,
# }
#
# merde_json::derive! {
#     impl(JsonSerialize, JsonDeserialize) for MyStruct { name, age }
# }
#
# fn main() -> Result<(), merde_json::MerdeJsonError> {
let original = MyStruct {
    _boo: Default::default(),
    name: "John Doe".into(),
    age: 30,
};

let serialized = original.to_json_string();
println!("{}", serialized);

let ms = merde_json::from_str(&serialized)?;
let ms: MyStruct = ms.to_rust_value()?;
assert_eq!(original, ms);
# Ok(())
# }

在序列化时减少分配

如果您想更精细地控制缓冲区,例如您想为多次序列化重用相同的 Vec<u8>,可以使用 JsonSerializer::from_vec

# use merde_json::{Fantome, JsonSerialize, JsonDeserialize, ToRustValue};
# use std::borrow::Cow;
#
# #[derive(Debug, PartialEq)]
# struct MyStruct<'src, 'val> {
#     _boo: Fantome<'src, 'val>,
#     name: Cow<'val, str>,
#     age: u8,
# }
#
# merde_json::derive! {
#     impl(JsonSerialize, JsonDeserialize) for MyStruct { name, age }
# }
#
# fn main() -> Result<(), merde_json::MerdeJsonError> {
let original = MyStruct {
    _boo: Default::default(),
    name: "John Doe".into(),
    age: 30,
};

let mut buffer = Vec::new();
for _ in 0..3 {
    buffer.clear();
    let mut serializer = merde_json::JsonSerializer::from_vec(buffer);
    original.json_serialize(&mut serializer);
    buffer = serializer.into_inner();

    let ms = merde_json::from_slice(&buffer)?;
    let ms = ms.to_rust_value()?;
    assert_eq!(original, ms);
}
# Ok(())
# }

请注意,序列化是不可失败的,因为它针对的是内存缓冲区而不是写入器,并且我们假设分配不会失败(像大多数当前的Rust代码一样)。

记住,如果 Vec 增长,除非您显式地通过 Vec::shrink_to_fitVec::shrink_to 等方式请求,否则它不会归还其内存。

注意事项与限制

这个crate的大部分内容都非常简单,这是故意的。

例如,由于反序列化是递归的,深层数据结构 确实 会耗尽堆栈。

反序列化会通过 jiter::JsonValue 进行,它包含诸如 std::sync::Arc、小型向量、懒哈希表等类型。简单地构建它们以从它们解构是浪费CPU周期,如果它在您的配置文件中出现了,那么是时候转向jiter的事件解析器 jiter::Jiter 了。

如果您期望一个 u32,但JSON负载是一个浮点数,它将被四舍五入。

如果您期望一个 u32,但JSON负载大于 u32::MAX,您将得到一个 MerdeJsonError::OutOfRange 错误。

对于JSON数字中的无穷大/NaN没有控制:您可以自己调用 jiter::JsonValue::parse 来解决这个问题。

序列化不能是可读的:它永远不会产生不必要的空格、换行符等。如果您的性能特性允许,您可以查看 formatjson

序列化可能会产生其他解析器会拒绝或错误解析的JSON负载,特别是对于大于2^53或小于-2^53的数字。

没有内置的功能来序列化和反序列化从数字到字符串。

如果 merde_json 对您不起作用,那么您的用例很可能不受支持,您应该查看 serde

常见问题解答

《JsonDeserialize》接口中的 Option 是怎么回事?

这允许 Option<T> 忽略缺失的值。所有其他实现应返回 MerdeJsonError::MissingValue,如果选项是 None,然后将其转换为 MerdeJsonError::MissingProperty 并带有字段名。

关于 #[serde(rename_all = "camelCase")] 我该怎么办?

将你的实际结构体字段改为 camelCase,并在你的结构体上添加 #[allow(non_snake_case)]。抱歉!

关于 #[serde(borrow)] 我该怎么办?

这是默认且唯一模式——对于所有字符串使用 Cow<'a, str>,如果你需要移动结构体,则使用 .to_static()

依赖项

~3.5MB
~70K SLoC