json 使用jq,如何合并两个(或更多)具有相同键且值为数组的对象,同时连接数组?

ldioqlga  于 2023-03-04  发布在  其他
关注(0)|答案(2)|浏览(214)

假设我们有两个json对象:
{"messages":["one"], "keyA": "valueA"}
以及
{"messages":["two"], "keyB": "valueB"}
我希望有一种方法可以在连接数组值的同时合并这两个对象,这样得到的对象将是:
{"messages":["one","two"], "keyA": "valueA", "keyB": "valueB"}
到目前为止,我所看到的大多数方法都不足以实现这一点,因为数组会被"最右边"对象的版本覆盖。
即:
echo '{"messages":["one"], "keyA": "valueA"}{"messages":["two"], "keyB": "valueB"}' | jq -s '.[0] * .[1]'
产生:

"messages": [
    "two"
  ],
  "keyA": "valueA",
  "keyB": "valueB"
}

(NOTE:messages数组值仅包含two(来自第二个(最右侧)对象)
从jq手册上的"add"命令:
对象是通过合并添加的,即将两个对象中的所有键-值对插入到一个组合对象中。如果两个对象包含同一个键的值,则+右边的对象优先。(对于递归合并,请使用 * 运算符。)
(着重号后加)
但是将+运算符更改为*似乎不会更改输出。
我看过jq: recursively merge objects and concatenate arrays,但是......哇......难道没有更好的办法吗?
如果解决方案可以像处理空数组一样处理数组键值为null的对象,则会加分:
{"messages":null, "keyA": "valueA"}{"messages":["two"], "keyB": "valueB"}

f2uvfpb9

f2uvfpb91#

我发现了几种方法:
首先,这是一个known issue
其次,从已知问题中我发现了这一点:
jq -s '[.[] | to_entries] | flatten | reduce .[] as $dot ({}; .[$dot.key] += $dot.value)'
例如:
echo '{"messages":["one"], "keyA": "valueA"}{"messages":["two"], "keyB": "valueB"}' | jq -s '[.[] | to_entries] | flatten | reduce .[] as $dot ({}; .[$dot.key] += $dot.value)'
产生:

{
  "messages": [
    "one",
    "two"
  ],
  "keyA": "valueA",
  "keyB": "valueB"
}

(as期望值)
这来自https://github.com/stedolan/jq/issues/502
此外,受https://github.com/stedolan/jq/issues/957的启发,我能够做到这一点:
echo '{"messages":["one"], "keyA": "valueA"}{"messages":["two"], "keyB": "valueB"}' | jq -s '.[2].messages = .[0].messages + .[1].messages | .[0] + .[1] + .[2]'
这也会产生预期的输出,但是是以一种非一般化的方式。将所需的合并数组存储在根数组的第三个元素中的灵感非常简洁(这就是我提到它的原因);则使用现有的"右侧获胜"行为将正确合并的数组插回到结果中。
最后,从同一个问题,有这样的:
echo '{"messages":["one"], "keyA": "valueA"}{"messages":["two"], "keyB": "valueB"}' | jq -s '.[0] as $o1 | .[1] as $o2 | ($o1 + $o2) | .messages = ($o1.messages + $o2.messages)'
其本质上是与上述临时存储方法相同的非一般化解决方案(但可能更优雅?)。

ijxebb2r

ijxebb2r2#

对于问题中的简单示例,以下筛选器提供了一个简单但有原则的方法,该方法也符合“奖励积分”的条件:

# Assumption: the input and $obj are objects that are array-consistent
# in the sense that if $k is a key of either object, then:
# (a) if .[$k] is an array then $obj[$k] is an array or null, and
# (b) if $obj[$k] is an array, then .[$k] is an array or null
# The output is . + $obj except that array-valued keys are concatenated.
# WARNING: the assumption is not checked.
def combine($obj):
  . as $in
  | reduce ($obj|keys_unsorted[]) as $key
      ($in;
       if (.[$key] | type) == "array" then .[$key] += $obj[$key]
       else .[$key] = $obj[$key]
       end ) ;

当然,这是不可交换的。
对于两个以上的对象,只需使用reduce,例如,对于对象数组:

def combine: if length==0 then . else reduce .[] as $_ ([]; combine($_)):

相关问题