【问题标题】:Aggregate json arrays from multiple files using jq, grouping by key使用 jq 从多个文件聚合 json 数组,按键分组
【发布时间】:2022-03-14 18:49:32
【问题描述】:

我想将两个或多个文件聚合成一个 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 属性并对其进行硬编码。有没有一种简单的方法可以在不显式使用键名的情况下获得相同的结果?

【问题讨论】:

  • 如果一个或多个文件中的顶级对象有多个键怎么办?
  • 我正在处理一个只有一个键的 api 响应。所以它不会影响这种情况,但对于一般情况,所有未命名为 shapes 的键都会被有效地丢弃,这可能是我想要的,也可能不是。

标签: json jq


【解决方案1】:

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

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

或更紧凑:

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 : unsorted 部分仅用于性能吗?在我的具体情况下,我真的不在乎,除了排序键在可读性方面有一点好处。
  • 如果顶级对象只有一个键,正如这个答案所假设的那样,那么在功能上当然没有区别;在计算上,差异可能非常小,甚至可能不存在。
【解决方案2】:

这里有一个解决方案,也解决了一个更普遍的问题:首先,它处理任意多个输入文件;其次,假设每个顶级键都是数组值的,它会为每个键形成键的“总和”。

通用函数:

  # 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] ));

为避免“啜饮”,我们将使用inputs

aggregate(inputs)

因此调用必须使用 -n 命令行选项:

jq -n -f program.jq *.json

【讨论】:

  • 嗨@peak,我无法重现该解决方案。我将该函数保存为 program.jq 并运行 jq -n -f program.jq *.json 但我得到了 null 作为响应。我想我必须用聚合语句做一些事情。
  • @moogly81 在文件aggregate(inputs) 附加行后尝试program.jq
  • 啊,好的,这行得通!聚合(输入)是在 program.jq 文件的末尾
【解决方案3】:

试试下面的代码。这可以处理任意数量的文件。所有输入都假定为 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"
    }
  ]
}

【讨论】:

  • 这非常有效。我不认为它是一种简单的方法,就像问题中所说的那样,但我想这并不容易做到。
  • 不幸的是,这个解决方案效率非常低(无论是在内存使用方面还是在速度方面),而且还存在问题(例如,因为它使用flatten 的方式)。
猜你喜欢
  • 2023-03-22
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多