[英]Update one JSON file values with values from another JSON using JQ (on all levels)
我有两个 JSON 文件:
源.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"
}
和
目标.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,考虑到各个层面。
我已经看到并测试了这篇文章中的解决方案。 它只复制第一级密钥,我无法让它做我需要的事情。 这篇文章中解决方案的结果如下所示:
{
"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”键下的所有内容都按原样复制。
我需要的是:
{
"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 中的键,也不应创建新键。
任何人都可以帮忙吗?
您可以采用的一种方法是获取每个输入的所有标量值的所有路径并采用设置的交集。 然后从这些路径将值从源复制到目标。
首先,我们需要一个 intersect 函数(这出奇地难以制作):
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
[注意:此回复针对原始数据回答了原始问题。 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
此外,我们可以使用paths(scalars)
tostream
代替tostream
。
将这些替代方案放在一起:
($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"
}
}
下面提供了修改后的问题的解决方案,它实际上是关于“路径”而不是“密钥”。
([$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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.