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
每月下载量 53,641 次
在 15 个 crate 中使用(直接使用 3 个)
67KB
1K SLoC
高保真 JSON 词法分析和解析器
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_json
和 hifijson
解析大量 JSON 数据到各自相应的 Value
类型所需的时间。为了更好的可比性,我启用了 serde_json
的 arbitrary_precision
标志,它将数字解析为字符串,就像 hifijson
一样。尽管如此,这种比较仍有些许牵强,因为 serde_json
的 Value
使用 String
来存储数字和字符串,而 hifijson
的 Value
使用 &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
在处理不包含转义序列的数字和字符串时更快,但在关键字(null
,true
,false
)和深层嵌套数组方面较慢。另外,请注意,在没有 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中定义)的连接。这允许出现一些奇怪的情况,如nulltruefalse
,1.0"a"
,但某些值无法重建,如1.042.0
,因为这可能是1.0和42.0的连接,也可能是1.04和2.0的连接。从这个意义上讲,hifijson
试图实现尽可能宽松和易于描述的政策。
此外,解析器通过了JSON解析测试套件的所有测试。
模糊测试
要运行模糊测试,请安装cargo-fuzz
。然后,如果您不想使用默认的nightly Rust编译器,可以通过运行cargo +nightly fuzz run all
来运行模糊测试。
依赖项
~185KB