#json-format #json-query #json-path #serde #jmespath #parser #json-search

jetro

Jetro 是一个用于转换、查询和比较 JSON 格式的工具

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 编码

MIT 许可证

130KB
3K SLoC

jetro

GitHub

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