5 个版本

0.2.2 2024年7月30日
0.2.1 2024年3月13日
0.2.0 2023年2月6日
0.1.1 2023年1月20日
0.1.0 2023年1月20日

解析器实现 中排名第 344

Download history 16790/week @ 2024-05-02 18197/week @ 2024-05-09 20598/week @ 2024-05-16 21845/week @ 2024-05-23 24471/week @ 2024-05-30 19330/week @ 2024-06-06 19181/week @ 2024-06-13 21873/week @ 2024-06-20 24470/week @ 2024-06-27 14621/week @ 2024-07-04 13999/week @ 2024-07-11 15112/week @ 2024-07-18 14509/week @ 2024-07-25 13538/week @ 2024-08-01 12688/week @ 2024-08-08 10371/week @ 2024-08-15

每月下载量 53,641
15 crate 中使用(直接使用 3 个)

MIT 许可协议

67KB
1K SLoC

高保真 JSON 词法分析和解析器

Build status Crates.io Documentation Rust 1.56+

hifijson 是一个 Rust crate,提供高保真 JSON 词法分析和解析器。在此上下文中,高保真意味着与其他许多解析器不同,hifijson 旨在非常忠实地保留输入数据,尤其是数字。

  • 无依赖:甚至 alloc 也不是必需的!
  • no_std:可以在没有标准库的嵌入式系统中使用。
  • 从切片和字节迭代器中读取:如果您正在编写一个应该从文件以及从标准输入读取的应用程序,这一点很重要。
  • 性能
  • 可移植性
  • 大部分为零拷贝反序列化:由于 JSON 字符串中存在转义字符,JSON 数据的完整零拷贝反序列化是不可能的。然而,hifijson 尽量减少字符串存在时的分配。
  • 通过 serde 进行反序列化

serde_json 的比较

serde_json 目前是 Rust 中最受欢迎的 JSON 解析器。然而,serde_json 存在一些不足之处

  • 数字可以以任意精度解析(通过 arbitrary_precision 特性标志),但不能反序列化(通过实现 Deserialize 特性)为除 serde_json::Value 之外的其他任何内容 #896。相反,必须先反序列化为 serde_json::Value,然后将其转换为其他内容,这会耗费时间。
  • 当使用 arbitrary_precision 时,serde_json 无法正确解析或拒绝某些输入;例如,它错误地将 {"$serde_json::private::Number": "1.0"} 解析为数字 1.0,并错误地拒绝 {"$serde_json::private::Number": "foo"}。我认为这两个都是错误,尽管它们已被知晓,但 serde_json 的维护者却 "对此行为表示满意"
  • serde_json 的行为可以通过功能标志在一定程度上进行定制。然而,这是一个相对不灵活的解决方案;例如,您可以使用 preserve_order 功能标志来指定是否保留对象中键的顺序,但当您有一个包含相同键多次的对象时,例如 {"a": 1, "a": 2} 时会发生什么?目前,serde_json 将其解析为 {"a": 2},默默地丢弃信息。如果您希望在这种情况失败怎么办?好吧,您可以自己实现 Deserialize。但是……如果您使用 arbitrary_precision,则不能这样做。哎。

如果您想序列化/反序列化现有的 Rust 数据类型,则可能需要使用 serde_json。然而,如果您想处理来自外部世界的任意 JSON,需要控制您要读取的输入类型,或者只是关心快速的构建时间和最小的依赖关系,那么 hifijson 可能是您的选择。

还有用于嵌入式 JSON 使用的 serde-json-core;然而,这个包不支持任意精度数字、从字节迭代器读取或字符串中的转义序列。

性能

cargo run --release --example bench 测量了 serde_jsonhifijson 解析大量 JSON 数据到各自相应的 Value 类型所需的时间。为了更好的可比性,我启用了 serde_jsonarbitrary_precision 标志,它将数字解析为字符串,就像 hifijson 一样。尽管如此,这种比较仍有些许牵强,因为 serde_jsonValue 使用 String 来存储数字和字符串,而 hifijsonValue 使用 &str 来存储数字,使用 Cow<str> 来存储字符串。这使得 hifijson 在 “pi” 和 “hello” 基准测试中具有优势,但在 “hello-world” 基准测试中处于劣势。

基准测试 大小 serde_json hifijson
null 47 MiB 549 ms 736 ms
pi 66 MiB 2484 ms 1383 ms
hello 76 MiB 1762 ms 1334 ms
hello-world 143 MiB 1786 ms 2933 ms
arr 28 MiB 970 ms 1056 ms
tree 39 MiB 2221 ms 2822 ms

结果参差不齐:虽然 hifijson 在处理不包含转义序列的数字和字符串时更快,但在关键字(nulltruefalse)和深层嵌套数组方面较慢。另外,请注意,在没有 arbitrary_precision 的情况下,serde_json 解析数字的速度要快得多。

欢迎提出如何提高 hifijson 性能的建议。 :)

词法分析器

编写一个 JSON 解析器 非常简单 —— 困难的部分实际上是词法分析。这就是为什么 hifijson 首先为你提供一个词法分析器,然后你可以用它来自己构建解析器。是的,就是你。你可以做到。 hifijson 尝试为你提供一些基本抽象来帮助你。例如,默认解析器用不到 40 行代码实现。

默认解析器

解析 JSON 是一个雷区,因为 JSON 标准在某些方面定义不明确或直接矛盾。因此,解析器必须做出某些决定,即接受哪些输入并拒绝哪些输入。

hifijson 随带一个默认解析器,可能对许多用例来说已经足够好。该解析器做出以下选择

  • 字符串验证:解析器验证字符串是否为有效的 UTF-8。
  • JSON值连接:许多JSON处理工具接受JSON文件中的多个根JSON值。例如,[] 42 true {"a": "b"}。然而,正式定义这些工具实际上接受或拒绝的内容并不简单。例如,serde_json接受[]"a",但拒绝42"a"。这个解析器的默认行为是接受任何可以重建的JSON-text(如RFC 8259中定义)的连接。这允许出现一些奇怪的情况,如nulltruefalse1.0"a",但某些值无法重建,如1.042.0,因为这可能是1.042.0的连接,也可能是1.042.0的连接。从这个意义上讲,hifijson试图实现尽可能宽松和易于描述的政策。

此外,解析器通过了JSON解析测试套件的所有测试。

模糊测试

要运行模糊测试,请安装cargo-fuzz。然后,如果您不想使用默认的nightly Rust编译器,可以通过运行cargo +nightly fuzz run all来运行模糊测试。

依赖项

~185KB