使用jq聚合多个文件中的JSON数组,按键分组

3

我希望将两个或多个文件合并成一个JSON文件,并将相同键名下的数组合并。

file1.json

{
  "shapes": [
    {
      "id": "1",
      "name": "circle"
    },
    {
      "id": "2",
      "name": "square"
    }
  ]
}

file2.json

{
  "shapes": [
    {
      "id": "3",
      "name": "triangle"
    }
  ]
}

预期结果:

{
  "shapes": [
    {
      "id": "1",
      "name": "circle"
    },
    {
      "id": "2",
      "name": "square"
    },
    {
      "id": "3",
      "name": "triangle"
    }
  ]
}

我可以用以下jq命令来实现此操作:
jq -s '{shapes: map(.shapes)|add }' file*.json

但这需要我知道shapes属性并硬编码它。是否有一种简单的方法可以在不显式地使用关键字名称的情况下获得相同的结果?


如果一个或多个文件中的顶层对象有多个键怎么办? - peak
我正在处理一个只有一个键的API响应。因此,这不会影响此情况,但对于一般情况,所有未命名为shapes的键都将被有效丢弃,这可能是我想要的,也可能不是。 - moogly81
3个回答

1
这里有一个解决方案,它还可以解决一个更一般的问题:首先,它处理任意数量的输入文件;其次,对于每个键,它按键形成“总和”,假设每个顶级键都是数组值。
通用函数:
  # the values at each key are assumed to be arrays
  def aggregate(stream): 
    reduce stream as $o ({}; 
      reduce ($o|keys_unsorted[]) as $k (.; 
        .[$k] += $o[$k] ));

为了避免 " slurping ",我们将使用 inputs:。
aggregate(inputs)

因此,调用必须使用-n命令行选项:
jq -n -f program.jq *.json

嗨@peak,我无法重现解决方案。我将函数保存为program.jq并运行jq -n -f program.jq *.json,但是我得到了null作为响应。我想我必须对聚合语句进行一些操作。 - moogly81
2
@moogly81 在文件 program.jq 中追加行 aggregate(inputs) 后再尝试。 - pii_ke
啊,好的,这行可以工作! aggregate(inputs)应该在程序.jq文件的结尾。 - moogly81

1

以下是一种适用于每个顶层对象只有一个键的解决方案,既高效又概念简单。它假定使用 -n 选项调用 jq。

reduce inputs as $in (null;
   ($in|keys_unsorted[0]) as $k | { ($k): (.[$k] + $in[$k]) })

或者稍微更紧凑一些:

(保留HTML,不解释)
reduce inputs as $in (null; ($in|keys_unsorted[0]) as $k | .[$k] += $in[$k] )

我正在更改我的已接受答案为这个,因为这是我将要使用的答案。jq -n 'reduce inputs as $in (null; ($in|keys[0]) as $k | .[$k] += $in[$k] )' file*.json @peak:未排序部分只是为了性能吗?在我的特定情况下,我并不真的关心,除了排序键对可读性的微小奖励。 - moogly81
如果顶层对象只有一个键,就像这个答案假设的那样,那么从功能上讲,它并没有什么区别;从计算上讲,差异可能非常小甚至不存在。 - peak

0
尝试以下代码。这可以处理任意数量的文件。假定所有输入都是具有数组内所有值的json对象。在按键分组后,所有这样的数组都会聚合。它输出一个具有与相应聚合数组相关联的键的对象。
jq -s 'map(to_entries)|add|group_by(.key)|
    map( { "key": (.[0].key), "value": (map(.value)|add)})|
    from_entries' file1.json file2.json

对于您的示例输入,这将给出:

{
  "shapes": [
    {
      "id": "1",
      "name": "circle"
    },
    {
      "id": "2",
      "name": "square"
    },
    {
      "id": "3",
      "name": "triangle"
    }
  ]
}

这个完美地运作。我不认为它是“简单的方法”,就像问题中所述,但我猜这并不容易做到。 - moogly81
很不幸,这个解决方案非常低效(在内存使用和速度方面都是如此),而且存在问题(例如因为它使用了“flatten”的方式)。 - peak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接