7 个版本
0.2.5 | 2024年3月27日 |
---|---|
0.2.4 | 2023年7月15日 |
0.2.1 | 2023年3月25日 |
0.1.0 | 2023年3月19日 |
#252 in 编码
130KB
3K SLoC
jetro
Jetro 是一个库,它提供了一个用于转换、查询和比较 JSON 格式数据的自定义 DSL。它易于使用和扩展。
Jetro 依赖最少,其遍历和评估算法是在 serde_json 的基础上实现的。
Jetro 可以通过编译为 WASM 在 Web 浏览器中使用。 克隆它 并尝试一下。
Jetro 可以通过使用 Jetrocli 在命令行中使用。
Jetro 将访问路径与在管道中匹配的值上操作的功能结合起来。访问路径使用 /
作为分隔符,类似于 URI 结构,访问路径的开始应表示是否从根开始使用 >
,可以使用 <
在嵌套路径中从根开始遍历。
Jetro 表达式支持换行和空格,可以将语句分解成更小的部分。
按照惯例,函数使用 #
运算符表示。函数可以组合。
函数 | 动作 |
---|---|
#pick('string' | expression, ...) [ as | as* 'binding_value' ] | 从对象中选择一个键,将其绑定到名称,选择多个子查询以创建新对象 |
#head | 列表的头部 |
#tail | 列表的尾部 |
#keys | 与对象关联的键 |
#values | 与对象关联的值 |
#reverse | 反转列表 |
#min | 数字的最小值 |
#max | 数字的最大值 |
#all | 所有布尔值是否为真 |
#sum | 数字的总和 |
#formats('format with placeholder {} {}', 'key_a', 'key_b') [ -> | ->* 'binding_value' ] | 将格式化的键:值插入到对象中或将其作为单个键:值返回 |
#filter('target_key' (>, <, >=, <=, ==, ~=, !=) (string, boolean, number)) | 在列表上执行筛选 |
使用给定lambda映射列表中的每个元素:#map(x: x.y.z | x.y.z.some_method()) | 使用给定的lambda映射列表中的每个项目 |
#zip | 将两个或更多数组组合在一起 |
let data = serde_json::json!({
"name": "mr snuggle",
"some_entry": {
"some_obj": {
"obj": {
"a": "object_a",
"b": "object_b",
"c": "object_c",
"d": "object_d"
}
}
}
});
let mut values = Path::collect(data, ">/..obj/#pick('a','b')");
#[derive(Serialize, Deserialize)]
struct Output {
a: String,
b: String,
}
let output: Option<Output> = values.from_index(0);
结构
Jetro由解析器、上下文包装器(管理用户输入的每个步骤的遍历和评估)和动态函数的运行时组成。未来版本将支持用户自定义函数。
示例
{
"customer": {
"id": "xyz",
"ident": {
"user": {
"isExternal": false,
"profile": {
"firstname": "John",
"alias": "Japp",
"lastname": "Appleseed"
}
}
},
"preferences": []
},
"line_items": {
"items": [
{
"ident": "abc",
"is_gratis": false,
"name": "pizza",
"price": 4.8,
"total": 1,
"type": "base_composable"
},
{
"ident": "def",
"is_gratis": false,
"name": "salami",
"price": 2.8,
"total": 10,
"type": "ingredient"
},
{
"ident": "ghi",
"is_gratis": false,
"name": "cheese",
"price": 2,
"total": 1,
"type": "ingredient"
},
{
"ident": "uip",
"is_gratis": true,
"name": "chilli",
"price": 0,
"total": 1,
"type": "ingredient"
},
{
"ident": "ewq",
"is_gratis": true,
"name": "bread sticks",
"price": 0,
"total": 8,
"type": "box"
}
]
}
}
查询
获取与line_items
相关联的值。
>/line_items
查看输出
结果
{
"items": [
{
"ident": "abc",
"is_gratis": false,
"name": "pizza",
"price": 4.8,
"total": 1,
"type": "base_composable"
},
{
"ident": "def",
"is_gratis": false,
"name": "salami",
"price": 2.8,
"total": 10,
"type": "ingredient"
},
{
"ident": "ghi",
"is_gratis": false,
"name": "cheese",
"price": 2,
"total": 1,
"type": "ingredient"
},
{
"ident": "uip",
"is_gratis": true,
"name": "chilli",
"price": 0,
"total": 1,
"type": "ingredient"
},
{
"ident": "ewq",
"is_gratis": true,
"name": "bread sticks",
"price": 0,
"total": 8,
"type": "box"
}
]
}
获取与第一个匹配的键相关联的值,并返回其id
字段。
>/('non-existing-member' | 'customer')/id
查看输出
结果
"xyz"
递归搜索具有指定值的键的对象。
>/..('type'='ingredient')
查看输出
结果
[
{
"ident": "ghi",
"is_gratis": false,
"name": "cheese",
"price": 2,
"total": 1,
"type": "ingredient"
},
{
"ident": "def",
"is_gratis": false,
"name": "salami",
"price": 2.8,
"total": 10,
"type": "ingredient"
}
]
>/..items/#tail
查看输出
结果
[
{
"ident": "def",
"is_gratis": false,
"name": "salami",
"price": 2.8,
"total": 10,
"type": "ingredient"
},
{
"ident": "ghi",
"is_gratis": false,
"name": "cheese",
"price": 2,
"total": 1,
"type": "ingredient"
},
{
"ident": "uip",
"is_gratis": true,
"name": "chilli",
"price": 0,
"total": 1,
"type": "ingredient"
},
{
"ident": "ewq",
"is_gratis": true,
"name": "bread sticks",
"price": 0,
"total": 8,
"type": "box"
}
]
>/..items/#filter('is_gratis' == true and 'name' ~= 'ChILLi')
查看输出
结果
[
{
"ident": "uip",
"is_gratis": true,
"name": "chilli",
"price": 0,
"total": 1,
"type": "ingredient"
}
]
>/..items/#filter('is_gratis' == true and 'name' ~= 'ChILLi')/#map(x: x.type)
查看输出
结果
[
"ingredient"
]
创建一个新的对象,其模式为{'total': ..., 'fullname': ...}
,如下所示
- 递归搜索
line_items
,进入任何匹配的对象,使用is_gratis == false
语句过滤匹配项,递归查找其价格,并返回价格总和 - 递归搜索对象
user
,选择其profile
,并使用以下模式创建一个新的对象:{'fullname': ...}
,通过连接键的值('firstname', 'lastname')进行格式化
>/#pick(
>/..line_items
/*
/#filter('is_gratis' == false)/..price/#sum as 'total',
>/..user
/profile
/#formats('{} {}', 'firstname', 'lastname') ->* 'fullname'
)
查看输出
结果
{
"fullname": "John Appleseed",
"total": 9.6
}
从数组的索引0中选择最多4个项目items
>/..items/[:4]
查看输出
结果
[
{
"ident": "abc",
"is_gratis": false,
"name": "pizza",
"price": 4.8,
"total": 1,
"type": "base_composable"
},
{
"ident": "def",
"is_gratis": false,
"name": "salami",
"price": 2.8,
"total": 10,
"type": "ingredient"
},
{
"ident": "ghi",
"is_gratis": false,
"name": "cheese",
"price": 2,
"total": 1,
"type": "ingredient"
},
{
"ident": "uip",
"is_gratis": true,
"name": "chilli",
"price": 0,
"total": 1,
"type": "ingredient"
}
]
从第4个索引开始选择,直到数组items
的末尾
>/..items/[4:]
查看输出
结果
[
{
"ident": "ewq",
"is_gratis": true,
"name": "bread sticks",
"price": 0,
"total": 8,
"type": "box"
}
]
创建一个新的对象,其模式为{'total_gratis': ...}
,如下所示
- 递归查找包含
items
的任何对象,然后在匹配的对象中递归搜索is_gratis
和匹配值的长度
>/#pick(>/..items/..is_gratis/#len as 'total_gratis')
查看输出
结果
{
"total_gratis": 2
}
递归搜索对象items
,选择其第一个项目,并返回其键
>/..items/[0]/#keys
查看输出
结果
[
"ident",
"is_gratis",
"name",
"price",
"total",
"type"
]
递归搜索对象items
,选择其第一个项目,并返回其值
>/..items/[0]/#values
查看输出
结果
[
"abc",
false,
"pizza",
4.8,
1,
"base_composable"
]
将两个或更多数组组合在一起。
>/#pick(>/..name as 'name',
>/..nested as 'field',
>/..b as 'release')/#zip
JSON
{
"a": [
{
"name": "tool",
"value": {
"nested": "field"
}
},
{
"name": "pneuma",
"value": {
"nested": "seal"
}
}
],
"b": [
2000,
2100
]
}
查看输出
结果
[
{
"field": "field",
"name": "tool",
"release": 2000
},
{
"field": "seal",
"name": "pneuma",
"release": 2100
}
]
依赖项
~4.5–7MB
~128K SLoC