高级 JSON 查询语言



我已经探索了几种现有的JSON查询语言,如JMESPath,JsonPath和JSONiq。不幸的是,它们似乎都无法以通用方式支持我的用例。

基本上,我收到来自不同 Web 服务的不同类型的响应。我需要让用户能够在其他二维数组中重新映射响应,以利用我们的可视化工具。根据新格式,用户可以决定如何在现有小部件之间显示其数据。非常像完全在UI上管理的可自定义仪表板。

无论如何,我的输入如下所示:

{
"category_1": [
{
"name": "medium",
"count": 10
},
{
"name": "high",
"count": 20
}
],
"category_2": [
{
"name": "medium",
"count": 30
},
{
"name": "high",
"count": 40
}
]
}

预期输出:

[
{
"name": "medium",
"count": 10,
"category": "1"
},
{
"name": "high",
"count": 20,
"category": "1"
},
{
"name": "medium",
"count": 30,
"category": "2"
},
{
"name": "high",
"count": 40,
"category": "2"
}
]

我越接近JMESPath,但我的查询根本不是动态的。用户需要了解可能的分组类别。

查询如下所示:[ category_1[].{name: name, count: count, category: '1'}, category_2[].{name: name, count: count, category: '2'} ] | []

换句话说,我需要足够强大的JSON查询语言来执行此JavaScript代码:

const output = flatMap(input, (value, key) => {
return value.map(x => {
return { ...x, category: key };
});
});

有什么想法吗?

这在JMESPath(0.15.x(中目前确实是不可能的。还有其他符合规范的JMESPath软件包(只需付出一些额外的努力(就可以满足您的要求。使用 NPM 包@metrichor/jmespath(打字稿实现(,您可以使用所需的函数对其进行扩展,如下所示:


import {
registerFunction,
search,
TYPE_ARRAY,
TYPE_OBJECT
} from '@metrichor/jmespath';
registerFunction(
'flatMapValues',
([inputObject]) => {
return Object.entries(inputObject).reduce((flattened, entry) => {
const [key, value]: [string, any] = entry;
if (Array.isArray(value)) {
return [...flattened, ...value.map(v => [key, v])];
}
return [...flattened, [key, value]];
}, [] as any[]);
},
[{ types: [TYPE_OBJECT, TYPE_ARRAY] }],
);

使用这些扩展函数,JMESPath 表达式现在将如下所示,以将键重新映射到每个值中:

search("flatMapValues(@)[*].merge([1], {category: [0]})", {
"category_1": [
{
"name": "medium",
"count": 10
},
{
"name": "high",
"count": 20
}
],
"category_2": [
{
"name": "medium",
"count": 30
},
{
"name": "high",
"count": 40
}
]
});
// OUTPUTS:
[
{
category: 'category_1',
count: 10,
name: 'medium',
},
{
category: 'category_1',
count: 20,
name: 'high',
},
{
category: 'category_2',
count: 30,
name: 'medium',
},
{
category: 'category_2',
count: 40,
name: 'high',
},
]

也就是说,您可以注册上面编写的函数并使用它

最后,使用Zorba实现管理JSONiq的方法。如果您需要强大的 JSON 查询,这绝对是要走的路。显然,这已经集成在Apache Spark和Rumble中。

无论如何,这是我的解决方案:

jsoniq version "1.0";
let $categories := 
{
"category_1": [
{
"name": "medium",
"count": 10
},
{
"name": "high",
"count": 20
}
],
"category_2": [
{
"name": "medium",
"count": 30
},
{
"name": "high",
"count": 40
}
]
}
for $key in keys($categories), $row in flatten($categories.$key)
return {"count": $row.count, "name": $row.name, "category": $key}

输出:

{ "count" : 10, "name" : "medium", "category" : "category_1" }{ "count" : 20, "name" : "high", "category" : "category_1" }{ "count" : 30, "name" : "medium", "category" : "category_2" }{ "count" : 40, "name" : "high", "category" : "category_2" }

你可以在这里尝试佐巴。

这是JSONiq 中的另一种可能性,它没有显式列出每行中的键,合并构造函数{| |}

jsoniq version "1.0";
let $categories := 
{
"category_1": [
{
"name": "medium",
"count": 10
},
{
"name": "high",
"count": 20
}
],
"category_2": [
{
"name": "medium",
"count": 30
},
{
"name": "high",
"count": 40
}
]
}
for $key in keys($categories),
$row in members($categories.$key)
return {|
$row,
{ "category": $key }
|}

为了完整起见,这是反向查询,它将输出转换回原始输入(使用分组依据子句(:

jsoniq version "1.0";
let $output :=
(
{ "count" : 10, "name" : "medium", "category" : "category_1" },
{ "count" : 20, "name" : "high", "category" : "category_1" },
{ "count" : 30, "name" : "medium", "category" : "category_2" },
{ "count" : 40, "name" : "high", "category" : "category_2" }
)
return
{|
for $row in $output
group by $category := $row.category
return { $category : [ $row ] }
|}

这很简单 ~Q (免责声明:我是开发人员(。

{
"results:{}:[]": [{
"{}:":".",
"category":"$key"
}]
}

输出:

{
"results": [
{
"name": "medium",
"count": 10,
"category": "category_1"
},
{
"name": "high",
"count": 20,
"category": "category_1"
},
{
"name": "medium",
"count": 30,
"category": "category_2"
},
{
"name": "high",
"count": 40,
"category": "category_2"
}
]
}

编辑:解释语法的更多信息:

"results:{}:[]"

:{} 部分表示"迭代对象中的所有键",:[] 表示"迭代所有数组元素"。

"{}:":"."

这会将当前对象中的每个字段复制到输出。

"category":"$key"

添加一个名为"类别"的字段,将当前遍历的键作为值。

如果我们想得到数字(即 1,2,...代替 category_1、category_2 等(,我们可以使用 substr:

"category": "$key substr(9)"

您实际上不需要任何额外的库。这是一个可以解决问题的小函数。您只需要拆分密钥。

const transform = (obj) => {
const ret = [];
for (let key in obj) {
const tmp = key.split('_');
for (let item of obj[key]) {
ret.push({
...item,
[tmp[0]]: tmp[1],
});
}
}
return ret;
};
const result = transform(obj);

最新更新