8 个版本

0.1.9 2024 年 5 月 15 日
0.1.8 2024 年 5 月 7 日
0.1.6 2024 年 4 月 28 日

#400解析实现

MIT 许可证

275KB
7K SLoC

大普特

大普特正在积极开发中,任何生产使用都可能遇到破坏性更改。如果您有任何建议或问题,请打开问题或 PR。

example workflow Crates.io Docs.rs

大普特(数据包)是一个 Rust 对象,允许使用 serde 包进行动态数据的序列化和反序列化。大普特允许您使用类似 jq 的语法遍历数据结构。该项目的目的是作为一个流处理引擎的数据包,该引擎处理非结构化数据,尽管它足够通用,可用于许多其他用途。

示例

use dapt::Dapt;

fn main() {
    let data = r#"
    {
        "name": "John Doe",
        "age": 30,
        "phones": [
            {
                "type": "home",
                "number": "212 555-1234"
            },
            {
                "type": "office",
                "number": "646 555-4567"
            }
        ]
    }

    // use serde_json to parse the data into a Dapt object
    let d: Dapt = serde_json::from_str(data).unwrap();

    // get just the phones numbers
    let name = d.get("phones[].number").unwrap();

    // write the data back out
    println!("{}", serde_json::to_string_pretty(&name).unwrap();
}

// output will be:
// [
//   "212 555-1234",
//   "646 555-4567"
// ]

使用 cargo run --example simple 运行它

匹配策略

大普特由两个关键部分组成

  • 一个字节数组,用于以二进制格式存储数据
  • 一个索引向量,指向该二进制数据中的位置

这意味着大普特数据包可以同时指向多个大普特数据包中的位置。作为用户,您可以使用 get 方法遍历大普特数据包。此方法遍历数据包,返回一个新的数据包,具有新的索引,这些索引与您提供的路径匹配。在此过程中不会复制底层二进制数据,从而减少堆分配。

大普特路径由用 . 分隔的节点组成。路径的每个部分都会检查文档以找到所有匹配的值。让我们通过上一个示例来更好地了解大普特的工作方式。我们的路径是 phones[].number。首先,大普特使用节点 phone 沿着文档向下遍历

[
  {
    "type": "home",
    "number": "212 555-1234"
  },
  {
    "type": "office",
    "number": "646 555-4567"
  }
]

接下来,大普特使用 [] 遍历数组。数组节点允许您指定索引,但由于我们没有指定,大普特数据包现在指向数组中的两个索引。

{
  "type": "home",
  "number": "212 555-1234"
}

{
  "type": "office",
  "number": "646 555-4567"
}

现在当使用 number 节点时,它将返回两个对象的 number 字段。这是因为我们指向的每个位置都会使用相同的节点进行遍历。

"212 555-1234"

"646 555-4567"

当我们让serde进行序列化时,dapt意识到它指向多个位置,并将序列化为它的指针数组。这些值不必是相同类型,请查看mixed示例,了解dapt如何处理这种情况。

可用路径节点

  • 字段字面量:例如 host.name 这是最基本的节点,它匹配与确切名称相匹配的字段。
  • 数组:例如 hosts[0].name 匹配数组中的所有索引。索引是可选的,如果提供,则只匹配具有该索引的项,否则匹配所有项
  • 数组通配符:例如 *.name 匹配映射的所有子节点。只深达一层
  • 递归:例如 ~.name 递归向下搜索匹配的节点。任何节点都可以跟随递归节点,唯一的要求是该节点有一些子节点
  • 正则表达式:例如 /^host.*/.name 匹配所有匹配正则表达式的字段。正则表达式是Rust正则表达式,并与字段名称进行匹配
  • 第一个:例如 host.{name,ip.*} 匹配返回值的第一个节点。匹配的每个子节点都是完整路径。如果子节点可能匹配多个值,它仍只匹配第一个值。
  • 多个:例如 host.(name|ip.*) 匹配所有指定的路径

查询功能

dapt具有在query模块中定义的查询功能。您可以使用Select结构来聚合数据,使用Filter结构来过滤数据,或者使用Query过滤器来利用类似SQL的查询实现。

查询

我本可以首先向您介绍过滤器和聚合,但让我们展示一下我们能够做什么。假设您有类似以下结构的数据进入

{
  "tickets_purchased": "3",
  "state": "NY",
  "name": "John Doe",
  "purchase_date": "2021-01-01",
}

然后您可以编写如下查询

SELECT
  sum("tickets_purchased") as "total.tickets",
  count() as "total.purchases",
  "name"
WHERE
  "state" IN ['NY', 'CA']
HAVING "total_tickets" > 20
GROUP BY "name"
ORDER BY "total_tickets" DESC
TOP 3

此查询将返回类似以下内容

[
  {
    "total": {
      "tickets": 30,
      "purchases": 10
    },
    "name": "John Doe"
  },
  {
    "total": {
      "tickets": 25,
      "purchases": 5
    },
    "name": "Jane Doe"
  },
  {
    "total": {
      "tickets": 20,
      "purchases": 5
    },
    "name": "John Smith"
  }
]

创建查询和收集数据接口非常简单

use dapt::query::Query;
use dapt::Dapt;

fn main() {
  let q = Query::new("SELECT sum(\"tickets_purchased\") as \"total.tickets\", count() as \"total.purchases\", \"name\" WHERE \"state\" IN ['NY', 'CA'] HAVING \"total.tickets\" > 20 GROUP BY \"name\" ORDER BY \"total.tickets\" DESC TOP 3");

  // load up some data
  let data: Vec<Dapt> = vec![
    serde_json::from_str(r#"{"tickets_purchased": "3", "state": "NY", "name": "John Doe", "purchase_date": "2021-01-01"}"#).unwrap(),
    serde_json::from_str(r#"{"tickets_purchased": "30", "state": "NY", "name": "John Doe", "purchase_date": "2021-01-01"}"#).unwrap(),
    serde_json::from_str(r#"{"tickets_purchased": "25", "state": "NY", "name": "Jane Doe", "purchase_date": "2021-01-01"}"#).unwrap(),
    serde_json::from_str(r#"{"tickets_purchased": "20", "state": "NY", "name": "John Smith", "purchase_date": "2021-01-01"}"#).unwrap(),
  ];

  for d in data {
    q.process(d);
  }

  let results = q.results();
}

依赖关系

~5-9MB
~160K SLoC