13个重大版本

0.14.0 2024年4月2日
0.13.0 2024年3月4日
0.12.0 2024年3月2日

589解析器实现

Download history 6/week @ 2024-05-20 14/week @ 2024-06-03 10/week @ 2024-06-10 78/week @ 2024-06-24 21/week @ 2024-07-01 51/week @ 2024-07-08 7/week @ 2024-07-15 24/week @ 2024-07-22 78/week @ 2024-07-29 94/week @ 2024-08-05

每月下载量203次

MIT 许可证

1.5MB
1.5K SLoC

JSN

一个具有低分配开销的可查询的流式JSON提取解析器。

  • 提取解析器? 解析器实现为一个迭代器,它发出标记
  • 流式? 正在解析的JSON文档永远不会完全加载到内存中。它是逐字节读取和验证的。这使得它非常适合处理大型JSON文档
  • 可查询的? 您可以配置解析器,以便只发出和为输入的感兴趣部分分配标记。

JSON应遵循 RFC 8259。然而,也支持换行符分隔的JSON连接的JSON格式。

输入可以来自任何实现了 Read 特性的来源(例如文件、字节切片、网络套接字等..)

基本用法

use jsn::{TokenReader, mask::*, Format};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let data = r#"
        {
            "name": "John Doe",
            "age": 43,
            "nicknames": [ "joe" ],
            "phone": {
                "carrier": "Verizon",
                "numbers": [ "+44 1234567", "+44 2345678" ]
            }
        }
        {
            "name": "Jane Doe",
            "age": 32,
            "nicknames": [ "J" ],
            "phone": {
                "carrier": "AT&T",
                "numbers": ["+33 38339"]
            }
        }
    "#;

    let mask = key("numbers").and(index(0))
        .or(key("name"))
        .or(key("age"));
    let mut iter = TokenReader::new(data.as_bytes())
        .with_mask(mask)
        .with_format(Format::Concatenated)
        .into_iter();

    assert_eq!(iter.next().unwrap()?, "John Doe");
    assert_eq!(iter.next().unwrap()?, 43);
    assert_eq!(iter.next().unwrap()?, "+44 1234567");
    assert_eq!(iter.next().unwrap()?, "Jane Doe");
    assert_eq!(iter.next().unwrap()?, 32);
    assert_eq!(iter.next().unwrap()?, "+33 38339");
    assert_eq!(iter.next(), None);

    Ok(())
}

快速说明

与传统流式解析器一样,解析器发出JSON标记。不同之处在于您可以用一种“有趣”的方式查询它们。最好的类比是位掩码

如果您可以使用位运算符 AND 来提取一个位模式

input   : 0101 0101
AND
bitmask : 0000 1111
=
pattern : 0000 0101

为什么不能使用位运算符 AND 来提取JSON标记模式?

input     : { "hello": { "name" : "world" } }
AND
json mask : {something that extracts a "hello" key}
=
pattern   : _ ________ { "name" : "world" } _

这个 {某种提取 ""hello"" 键的 } 是这个crate提供的。

内存占用

jsn 允许您选择感兴趣的JSON部分。您如何处理这些部分以及它们在内存中保留多长时间由您决定。

为了说明这一点,我将使用Valgrind DHAT工具来分析两个类似程序的堆内存使用情况。这两个程序都从JSON文件中读取和提取键。我将使用来自这里的sf-city-lots json文件(189 MB)。

  • examples/store-tokens.rs:此程序将提取的标记保留在Vec中
  • examples/print-tokens.rs:此程序按遇到顺序打印标记
valgrind --tool=dhat ./target/profiling/examples/store-tokens ~/downloads/citylots.json
# ==1146722== Total:     13,823,524 bytes in 196,541 blocks
# ==1146722== At t-gmax: 7,529,044 bytes in 196,515 blocks
valgrind --tool=dhat ./target/profiling/examples/print-tokens ~/downloads/citylots.json
# ==1152944== Total:     1,240,708 bytes in 196,524 blocks
# ==1152944== At t-gmax: 9,367 bytes in 9 blocks

第一个数字(总计)是程序在执行过程中分配的总堆内存量。

第二个数字(At t-gmax)是执行过程中任何时刻分配的最大内存量

不出所料,store-tokens.rs的内存占用更大。然而,由于该crate的总内存分配(13 MB)仍然比文件大小(189 MB)小一个数量级,因此其实用性仍然明显。

当您可以直接操作产生的令牌时(即,您不累积它们),情况会变得更好。这不仅使您的总分配更少,而且您的内存占用也小得多。print-tokens.rs在处理文件时,在任何时刻最多使用7KB的堆内存。

依赖项