简体   繁体   English

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

[英]Update one JSON file values with values from another JSON using JQ (on all levels)

I have two JSON files:我有两个 JSON 文件:

source.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"
}

and the

target.json:目标.json:

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

I need all the values for keys which exist in both files, copied from source.json to target.json , considering all the levels.我需要的所有值存在于这两个文件的密钥复制从source.jsontarget.json,考虑到各个层面。
I've seen and tested the solution from this post .我已经看到并测试了这篇文章中的解决方案。 It only copies the first level of keys, and I couldn't get it to do what I need.它只复制第一级密钥,我无法让它做我需要的事情。 The result from solution in this post , looks like this: 这篇文章中解决方案的结果如下所示:

{
  "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"
}

Everything under the "general" key was copied as is. “general”键下的所有内容都按原样复制。
What I need, is this:我需要的是:

{
  "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"
  }
}

Only "key1" and "key3" should be copied.只应复制“key1”和“key3”。
Keys in target JSON must not be deleted and new keys should not be created.不得删除目标 JSON 中的键,也不应创建新键。

Can anyone help?任何人都可以帮忙吗?

One approach you could take is get all the paths to all scalar values for each input and take the set intersections.您可以采用的一种方法是获取每个输入的所有标量值的所有路径并采用设置的交集。 Then copy values from source to target from those paths.然后从这些路径将值从源复制到目标。

First we'll need an intersect function (which was surprisingly difficult to craft):首先,我们需要一个 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);

Then to do the update:然后进行更新:

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

[Note: this response answers the original question, with respect to the original data. [注意:此回复针对原始数据回答了原始问题。 The OP may have had paths in mind rather than keys.] OP 可能考虑的是路径而不是密钥。]

There is no need to compute the intersection to achieve a reasonably efficient solution.无需计算交集即可获得合理有效的解决方案。

First, let's hypothesize the following invocation of jq:首先,让我们假设 jq 的以下调用:

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

In the file copy.jq, we can begin by defining a helper function:在文件 copy.jq 中,我们可以从定义一个辅助函数开始:

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

In order to inspect all the paths to leaf elements of $source , we can use tostream :为了检查$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)

Alternatives备择方案

Since $t is sorted, it would (at least in theory) make sense to use bsearch instead of index :由于 $t 已排序,因此(至少在理论上)使用bsearch而不是index有意义的:

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

Also, instead of tostream we could use paths(scalars) .此外,我们可以使用paths(scalars) tostream代替tostream

Putting these alternatives together:将这些替代方案放在一起:

($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)

Output输出

{
  "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"
  }
}

The following provides a solution to the revised question, which is actually about "paths" rather than "keys".下面提供了修改后的问题的解决方案,它实际上是关于“路径”而不是“密钥”。

([$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 is called so that binary search can be used subsequently.调用unique以便随后可以使用二进制搜索。

Invocation:调用:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM