我已经探索了几种现有的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);