高级 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 ] }
|}