[博客翻译]eserde——一个永不停歇的serde


原文地址:https://github.com/mainmatter/eserde/tree/155af5fb3df9d998e6b6bb70aecb7ca49e9f07d5/eserde


Eserde:不止步于第一个反序列化错误

Eserde 是一个专门解决 Rust 中反序列化错误报告问题的库。与传统的 serde 不同,它能够一次性报告多个错误,而不是在遇到第一个错误时就终止。这极大地改善了开发者体验,减少了调试和修正的时间。


什么是问题?

Rust 的 serde 库是目前最流行的(反)序列化工具。但它的设计有一个缺陷:当发生反序列化错误时,serde 会立即停止并只返回第一个错误信息。这对用户提交的数据处理(如 REST API 请求体)来说是一个大问题。例如,如果用户提交了一个包含多处错误的 JSON 数据,API 只能一次反馈一个错误,迫使用户进入慢且令人沮丧的反馈循环:

  1. 发送请求。
  2. 收到一个错误。
  3. 修复错误。
  4. 回到第 1 步,直到所有错误都被修复。

这种方式对开发者来说并不友好。我们应该可以同时报告多个错误,从而减少 API 交互次数,提高效率。


案例分析:无效的 JSON 数据

考虑以下结构作为参考示例:

#[derive(Debug, serde::Deserialize)]
struct Package {
    version: Version,
    source: String,
}

#[derive(Debug, eserde::Deserialize)]
struct Version {
    major: u32,
    minor: u32,
    patch: u32,
}

我们尝试通过 serde_json 对一个无效的 JSON 数据进行反序列化:

{
    "version": {
        "major": 1,
        "minor": "2"
    },
    "source": null
}

代码如下:

let payload = r#"
{
    "version": {
        "major": 1,
        "minor": "2"
    },
    "source": null
}"#;
let error = serde_json::from_str::<Package>(&payload).unwrap_err();
assert_eq!(
    error.to_string(),
    r#"invalid type: string "2", expected u32 at line 5 column 24"#
);

正如预期,serde_json 只返回了第一个错误:“字段 version.minor 类型错误”。

但是,这段 JSON 实际上还有其他问题:

  • version 结构缺少 patch 字段。
  • source 字段不能为 null

切换到 eserde 后,我们可以一次性捕获所有这些错误:

#[derive(Debug, eserde::Deserialize)]
struct Package {
    version: Version,
    source: String,
}

#[derive(Debug, eserde::Deserialize)]
struct Version {
    major: u32,
    minor: u32,
    patch: u32,
}

let payload = r#"
{
    "version": {
        "major": 1,
        "minor": "2"
    },
    "source": null
}"#;
let errors = eserde::json::from_str::<Package>(&payload).unwrap_err();

assert_eq!(
    errors.to_string(),
    r#"Something went wrong during deserialization:
- version.minor: invalid type: string "2", expected u32 at line 5 column 24
- version: missing field `patch`
- source: invalid type: null, expected a string at line 7 column 22
"#
);

现在,我们可以在一次反馈中告诉用户需要修复的三个问题。


如何使用 eserde

要在项目中使用 eserde,只需将以下依赖项添加到你的 Cargo.toml 文件中:

[dependencies]
eserde = { version = "0.1" }
serde = "1"

接下来,你需要:

  • 将所有的 #[derive(serde::Deserialize)] 替换为 #[derive(eserde::Deserialize)]
  • 切换到基于 eserde 的反序列化函数。

JSON 支持

eserde 提供了对 JSON 的原生支持,启用方法是在 Cargo.toml 中激活 json 特性:

[dependencies]
eserde = { version = "0.1", features = ["json"] }
serde = "1"

如果你正在处理 JSON 数据:

  • 替换 serde_json::from_streserde::json::from_str
  • 替换 serde_json::from_sliceeserde::json::from_slice

注意,eserde::json 不支持从读取器反序列化(即没有 serde_json::from_reader 的等价功能)。

此外,eserde 还提供了与 axum 的集成模块 eserde_axum,可以用作 axum 内置 JSON 解析器的替代方案。


兼容性

eserde 设计上最大程度地与 serde 兼容。derive(eserde::Deserialize) 会同时实