jq合并多个json文件,合并任何数组元素

时间:2019-06-18 14:45:53

标签: json jq

我想使用jq合并多个文件,并且如果多个文件包含名称相同的数组,则需要合并数组(顺序无关紧要)。

例如

file1

{
    "value1": 200,
    "timestamp": 1382461861,
    "parameter": [
    {"param": 1}
    ]
}

file2

{
    "status": 200,
    "timestamp": 1382461861,
    "value": {
        "aaa": {
            "value3": "v3",
            "value4": 4
        }
    },
    "parameter" [
    {"param": 2}
    ]
}

建议其他堆栈溢出文章合并这些json,我应该这样做:

jq -s '.[0] * .[1]' file1 file2

但是这让我得到了

{
  "value1": 200,
  "timestamp": 1382461861,
  "parameter": [
    {
      "param": 2
    }
  ],
  "status": 200,
  "value": {
    "aaa": {
      "value3": "v3",
      "value4": 4
    }
  }
}

我想要的地方是

{
  "value1": 200,
  "timestamp": 1382461861,
  "parameter": [
    { "param": 1}, 
    { "param": 2}
  ],
  "status": 200,
  "value": {
    "aaa": {
      "value3": "v3",
      "value4": 4
    }
  }
}

请注意,“参数”数组应同时包含file1和file2中的元素

我还需要一种不需要指定数组字段的解决方案,并且可以将数组嵌套在json的任何级别

我愿意接受不使用jq的解决方案,我想可以使用一个小的python脚本

我找到的最接近的解决方案要求我知道参数是一个数组

 jq -s '.[0] *  .[1]' file1.json file2.json >temp.json
 jq -s '.[0].parameter=([.[].parameter]|flatten)|.[0]' temp.json file1.json

,输出为

{
  "value1": 200,
  "timestamp": 1382461861,
  "parameter": [
    {
      "param": 2
    },
    {
      "param": 1
    }
  ],
  "status": 200,
  "value": {
    "aaa": {
      "value3": "v3",
      "value4": 4
    }
  }
}

3 个答案:

答案 0 :(得分:1)

您基本上是在实现自己的合并方案。如果需要通用解决方案,则需要定义一个函数,以便可以递归地完成它。这与“ *不太一样,但是具有不同的数组语义”,但是您可以使用如下代码:

def new_merge($item):
    if type == ($item|type) then   # if same types
        if type == "array" then        # concatenate the arrays
            . + $item
        elif type == "object" then     # recursively merge objects
            reduce ($item|to_entries[]) as {$key,$value} (.;
                .[$key] |= new_merge($value)
            )
        else                           # just take the "other" value
            $item // .
        end
    else                           # just take the "other" value
        $item // .
    end
    ;

我会将其放入您的~/.jq文件中,并按以下方式调用:

$ jq 'reduce inputs as $i (.; do_merge($i))' file*.json
{
  "value1": 200,
  "timestamp": 1382461861,
  "parameter": [
    {
      "param": 1
    },
    {
      "param": 2
    }
  ],
  "status": 200,
  "value": {
    "aaa": {
      "value3": "v3",
      "value4": 4
    }
  }
}

另一方面,如果您希望像在原始问题中那样递归合并数组项,只需更改数组大小写以递归合并相应项即可。

def new_merge2($item):
    if type == ($item|type) then
        if type == "array" then
            [.,$item] | transpose[] as [$a,$b] | [$a | new_merge2($b)]
        elif type == "object" then
            reduce ($item|to_entries[]) as {$key,$value} (.;
                .[$key] |= new_merge2($value)
            )
        else
            $item // .
        end
    else
        $item // .
    end
    ;

此版本将产生:

{
  "value1": 200,
  "timestamp": 1382461861,
  "parameter": [
    {
      "param1": 1,
      "param2": 2
    }
  ],
  "status": 200,
  "value": {
    "aaa": {
      "value3": "v3",
      "value4": 4
    }
  }
}

答案 1 :(得分:1)

在有两个文件(每个文件都有一个对象)的情况下,这是一个解决问题的方法,在该对象中至少有一个同名的数组值字段在某个级别上。

许多方面的要求不清楚,因此以下内容可能需要根据更详细的要求进行调整。如果文件多于两个,则可以使用相同的技术,但是详细信息将取决于详细要求。

jq -n --slurpfile file1 file1.json --slurpfile file2 file2.json '

  # a and b are expected to be jq paths ending with a string
  # emit the array of the intersection of key names
  def common(a;b):
    ((a|map(.[-1])) + (b|map(.[-1]))) 
    | unique;

  $file1[0] as $f1
  | $file2[0] as $f2
  | [$f1 | paths as $p | select(getpath($p) | type == "array") | $p] as $p1
  | [$f2 | paths as $p | select(getpath($p) | type == "array") | $p] as $p2
  | $f1+$f2
  | if ($p1|length) > 0 and ($p2|length) > 0 
    then common($p1; $p2) as $both
    | if ($both|length) > 0 
      then first( $p1[] | select(.[-1] == $both[0])) as $p1
      |    first( $p2[] | select(.[-1] == $both[0])) as $p2
      | ($f1 | getpath($p1)) as $a1
      | ($f2 | getpath($p2)) as $a2
      | setpath($p1; $a1 + $a2)
      else .
      end
    else .
    end
  '

输出

使用给定的输入,在第二个文件中添加缺少的“:”后,输出为:

{
  "value1": 200,
  "timestamp": 1382461861,
  "parameter": [
    {
      "param1": 1
    },
    {
      "param2": 2
    }
  ],
  "status": 200,
  "value": {
    "aaa": {
      "value3": "v3",
      "value4": 4
    }
  }
}

答案 2 :(得分:0)

在两个文件(每个文件有一个对象)的情况下,这是一个简单但通用的解决方案。

此解决方案将在同一路径上连接每对数组。希望它很简单,足以说明一切,并可以进行修改以应对各种更详细的要求。

jq -n --slurpfile file1 file1.json --slurpfile file2 file2.json '
  $file1[0] as $f1
  | $file2[0] as $f2
  | reduce ($f1 | paths) as $p ($f1+$f2;
      ($f1|getpath($p)) as $v1
      | ($f2|getpath($p)) as $v2
      | if ($v1 | type == "array") and
           ($v2 | type == "array")
        then setpath($p; $v1 + $v2)
        else .
        end)

'

输出完全符合要求,假设第二个文件已通过显而易见的方式更正,因此它是有效的JSON。