使用Boost-Spirit解析jsonpath

jsonpath是一种从json中抽取信息的工具,能够降低json数据处理复杂度,提高可读性与可维护性。jsonpath之于json相当于Xpath之于XML。为减少业务需求中频繁处理json数据逻辑的痛点,为nlohmann/json库手码jsonpath功能。

支持功能

costan225/jsonpath除函数调用功能(C++可以更高效直接操作target data得到统计数据)外,支持大部分jsonpath标准。另外根据实际需求,添加了数据替换功能,能够对抽取结果做修改。

用到的核心库:Boost-Spirit X3, Range-V3。Spirit是Boost的解析器框架,遵循Extended Backus Naur Form (EBNF) 规范,Spirit-X3是Spirit新一代版本,优化了编译时间(极大解放了生产力)和runtime性能。Range-V3是c++20 Ranges库的source,是FP风格编码的理想选择。

支持语法功能

功能示例 是否支持 功能描述
.prop 名为prop的子节点
[prop] 名为prop的子节点
* 通配符, 当前节点的所有孩子节点
..prop 递归搜索名为prop的子节点
[beg:step:end] 数组[beg,end),步长step可选
[?(expression)] 过滤器表达式,用于数组
.func() 函数,可供result对象调用

支持过滤器功能

过滤器功能 是否支持 功能描述
@ 过滤器引用当前节点
$ 过滤器引用根节点
prop1 > prop2 比较操作符,支持 >/</==/>=/<=
prop1 ~= regex 正则表达式
exp1 && exp2 关系操作符,支持
prop in (set) in nin 关键字

使用及示例

样本数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"expensive": 10
}

语法示例

jsonpath result
$.store.bicycle.color [“red”]
$.store.book[0,-1].price [8.95, 22.99]
$.store.book[-2:].price [8.99, 22.99]
$.store.book[0:3:2].price [8.95, 8.99]
$.store.book[?(@.isbn&&@.title==’Moby Dick’)].price [8.99]
$.store.book[?(@.price<$.expensive)].price [8.95, 8.99]
$.store.book[?(@.title~=/Sword.*/i)].author [“Evelyn Waugh”]
$..price [19.95, 8.95, 12.99, 8.99, 22.99]

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
// header files
#include "json/json.hpp"
#include "jsonpath/jsonpath.hpp"

using nlohmann
using coatan::jsonpath;

jsonpath p("$.school.rank");
if(p.valid()) {
json j; // sample data
json r = p.val(j); // result
}

与golang版本性能对比

选取github上star较高并支持filter的oliveagle/jsonpath库作对比。批量执行100000次查找。

C++版本

1
2
3
4
auto p = jsonpath("$.store.book[?(@.price<$.expensive)]");
for(int i = 0; i < 100000; ++i) {
auto res = p.eval(j);
}
1
2
time ./jsonpath-c++
./jsonpath-c++ 0.90s user 0.00s system 99% cpu 0.907 total

golang 版本

1
2
3
4
pat, _ := jsonpath.Compile(`$.store.book[?(@.price < $.expensive)]`)
for i := 0; i < 100000; i++ {
res, _ = pat.Lookup(json_data)
}
1
2
time ./jsonpath-golang
./jsonpath-golang 2.59s user 0.03s system 104% cpu 2.508 total
------ 本文结束------