使用JQ(在所有级别上)使用来自另一个JSON的值更新一个JSON文件值

时间:2018-10-17 11:38:49

标签: json merge jq

我有两个JSON文件:

  

source.json:

{
  "general": {
    "level1": {
      "key1": "x-x-x-x-x-x-x-x",
      "key3": "z-z-z-z-z-z-z-z",
      "key4": "w-w-w-w-w-w-w-w"
    },
    "another" : {
      "key": "123456",
      "comments": {
        "one": "111",
        "other": "222"
      }
    }
  },
  "title": "The best"
}

  

target.json:

{
  "general": {
    "level1": {
      "key1": "xxxxxxxx",
      "key2": "yyyyyyyy",
      "key3": "zzzzzzzz"
    },
    "onemore": {
      "kkeeyy": "0000000"
    }
  },
  "specific": {
    "stuff": "test"
  },
  "title": {
    "one": "one title",
    "other": "other title"
  }
}

考虑到所有级别,我需要两个文件中都存在的键的所有值,并将它们从 source.json 复制到 target.json
我已经查看并测试了this post的解决方案。 它仅复制第一级密钥,而我无法让它做我需要的事情。 this post中解决方案的结果如下:

{
  "general": {
    "level1": {
      "key1": "x-x-x-x-x-x-x-x",
      "key3": "z-z-z-z-z-z-z-z",
      "key4": "w-w-w-w-w-w-w-w"
    },
    "another": {
      "key": "123456",
      "comments": {
        "one": "111",
        "other": "222"
      }
    }
  },
  "specific": {
    "stuff": "test"
  },
  "title": "The best"
}

“常规”键下的所有内容均按原样复制。
我需要的是这个

{
  "general": {
    "level1": {
      "key1": "x-x-x-x-x-x-x-x",
      "key2": "yyyyyyyy",
      "key3": "z-z-z-z-z-z-z-z"
    },
    "onemore": {
      "kkeeyy": "0000000"
    }
  },
  "specific": {
    "stuff": "test"
  },
  "title": {
    "one": "one title",
    "other": "other title"
  }
}

仅应复制“ key1”和“ key3”。
不得删除目标JSON 中的密钥,也不应创建新密钥。

有人可以帮忙吗?

3 个答案:

答案 0 :(得分:1)

您可以采用的一种方法是,获取每个输入的所有标量值的所有路径,并采用交集。然后从这些路径中将值从源复制到目标。

首先,我们需要一个相交函数(很难制作):

def set_intersect($other):
    (map({ ($other[] | tojson): true }) | add) as $o
    | reduce (.[] | tojson) as $v ({}; if $o[$v] then .[$v] = true else . end)
    | keys_unsorted
    | map(fromjson);

然后进行更新:

$ jq --argfile s source.json '
reduce ([paths(scalars)] | set_intersect([$s | paths(scalars)])[]) as $p (.;
    setpath($p; $s | getpath($p))
)
' target.json

答案 1 :(得分:1)

[注意:此回复针对原始数据回答了原始问题。 OP可能会考虑路径而不是键。]

无需计算交集即可获得合理有效的解决方案。

首先,让我们假设jq的以下调用:

jq -n --argfile source source.json --argfile target target.json -f copy.jq

在文件copy.jq中,我们可以先定义一个辅助函数:

# emit an array of the distinct terminal keys in the input entity
def keys: [paths | .[-1] | select(type=="string")] | unique;

为了检查到$source的叶元素的所有路径,我们可以使用tostream

($target | keys) as $t
| reduce ($source|tostream|select(length==2)) as [$p,$v]
    ($target;
     if $t|index($p[-1]) then setpath($p; $v) else . end)

替代品

由于$ t已排序,因此(至少在理论上)使用bsearch代替index是有意义的:

 bsearch($p[-1]) > -1

此外,我们可以使用tostream代替paths(scalars)

将这些替代品放在一起:

($target | keys) as $t
| reduce ($source|paths(scalars)) as $p
    ($target;
     if $t|bsearch($p[-1]) > -1 
     then setpath($p; $source|getpath($p))
     else . end)

输出

{
  "general": {
    "level1": {
      "key1": "x-x-x-x-x-x-x-x",
      "key2": "yyyyyyyy",
      "key3": "z-z-z-z-z-z-z-z"
    },
    "onemore": {
      "kkeeyy": "0000000"
    }
  },
  "specific": {
    "stuff": "test"
  }
}

答案 2 :(得分:0)

以下内容为修订后的问题提供了解决方案,该问题实际上是关于“路径”而不是“键”的。

([$target|paths(scalars)] | unique) as $paths
| reduce ($source|paths(scalars)) as $p
    ($target;
     if $paths | bsearch($p) > -1 
     then setpath($p; $source|getpath($p))
     else . end)

unique被调用,以便随后可以使用二进制搜索。

调用:

jq -n --argfile source source.json --argfile target target.json -f program.jq