高级 JSON 查询语言
advanced JSON query language
我探索了一些现有的 JSON 查询语言,例如 JMESPath, JsonPath and JSONiq。不幸的是,none 他们似乎能够以通用方式支持我的用例。
基本上,我收到来自不同网络服务的不同类型的响应。我需要让用户能够重新映射二维数组中的响应,以利用我们的可视化工具。基于新格式,用户可以决定如何在现有小部件之间显示他的数据。非常类似于完全在 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 };
});
});
有什么想法吗?
最后,使用 Zorba 实现管理 JSONiq 的方法。如果您需要强大的 JSON 查询,这绝对是必经之路。显然这已经与 Rumble
集成在 Apache Spark 中
无论如何,这是我的解决方案:
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" }
你可以试试左巴here。
目前在 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',
},
]
也就是说你可以注册你上面写的函数并使用它
您实际上不需要任何额外的库。这是一个可以解决问题的小函数。您只需要拆分密钥。
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);
这是 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 }
|}
为了完整起见,这是将输出返回到原始输入(使用 group by 子句)的反向查询:
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 ] }
|}
我探索了一些现有的 JSON 查询语言,例如 JMESPath, JsonPath and JSONiq。不幸的是,none 他们似乎能够以通用方式支持我的用例。
基本上,我收到来自不同网络服务的不同类型的响应。我需要让用户能够重新映射二维数组中的响应,以利用我们的可视化工具。基于新格式,用户可以决定如何在现有小部件之间显示他的数据。非常类似于完全在 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 };
});
});
有什么想法吗?
最后,使用 Zorba 实现管理 JSONiq 的方法。如果您需要强大的 JSON 查询,这绝对是必经之路。显然这已经与 Rumble
集成在 Apache Spark 中无论如何,这是我的解决方案:
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" }
你可以试试左巴here。
目前在 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',
},
]
也就是说你可以注册你上面写的函数并使用它
您实际上不需要任何额外的库。这是一个可以解决问题的小函数。您只需要拆分密钥。
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);
这是 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 }
|}
为了完整起见,这是将输出返回到原始输入(使用 group by 子句)的反向查询:
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 ] }
|}