如何递归合并继承的 json 数组元素?

How to recursively merge inherited json array elements?

我有以下名为 CMakePresets.json 的 json 文件,它是一个 cmake 预设文件:

{
  "configurePresets": [
    {
      "name": "default",
      "hidden": true,
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/_build/${presetName}",
      "cacheVariables": {
        "YIO_DEV": "1",
        "BUILD_TESTING": "1"
      }
    },
    {
      "name": "debug",
      "inherits": "default",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Debug"
      }
    },
    {
      "name": "release",
      "inherits": "default",
      "binaryDir": "${sourceDir}/_build/Debug",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release"
      }
    },
    {
      "name": "arm",
      "inherits": "debug",
      "cacheVariables": {
        "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/Toolchain/arm-none-eabi-gcc.cmake"
      }
    }
  ]
}

我想与 * 递归合并为特定条目 name 继承自身的 configurePresets 元素。我有一个名为 arm 的节点示例,并希望生成具有已解析继承的 json 对象。父元素的名称存储在每个元素的 .inherits 中。 arm 继承了 debug 继承了 default.

and this answer:

的帮助下,我可以编写一个我认为有效的 bash shell 循环
input=arm
# extract one element
g() { jq --arg name "" '.configurePresets[] | select(.name == $name)' CMakePresets.json; };
# get arm element
acc=$(g "$input");
# If .inherits field exists
while i=$(<<<"$acc" jq -r .inherits) && [[ -n "$i" && "$i" != "null" ]]; do
   # remove it from input
   a=$(<<<"$acc" jq 'del(.inherits)');
   # get parent element
   b=$(g "$i");
   # merge parent with current
   acc=$(printf "%s\n" "$b" "$a" | jq -s 'reduce .[] as $item ({}; . * $item)');
done;
echo "$acc"

输出,我认为这是 arm:

的预期输出
{
  "name": "arm",
  "hidden": true,
  "generator": "Ninja",
  "binaryDir": "${sourceDir}/_build/${presetName}",
  "cacheVariables": {
    "YIO_DEV": "1",
    "BUILD_TESTING": "1",
    "CMAKE_BUILD_TYPE": "Debug",
    "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/Toolchain/arm-none-eabi-gcc.cmake"
  }
}

但是我想写在jq里。我试过了 jq 语言对我来说不直观。例如,我可以为两个(即可数)元素做到这一点:

< CMakePresets.json jq --arg name "arm" '
   def g(n): .configurePresets[] | select(.name == n);
   g($name) * (g($name) | .inherits) as $name2 | g($name2)
'

但是我不知道怎么办reduce .[] as $item ({}; . * $item)$item真的是g($name)这取决于最后的g($name) | .inherits。我尝试阅读 jq manual 并了解变量和循环,但 jq 的语法非常不同。我尝试使用 while,但这只是我不理解也不知道如何修复的语法错误。我猜 whileuntil 可能不在这里,因为它们对先前的循环输出进行操作,而元素总是来自根。

$ < CMakePresets.json jq --arg name "arm" 'def g(n): .configurePresets[] | select(.name == n);
while(g($name) | .inherits as $name; g($name))   
'
jq: error: syntax error, unexpected ';', expecting '|' (Unix shell quoting issues?) at <top-level>, line 2:
while(g($name) | .inherits as $name; g($name))                                      
jq: 1 compile error

如何用jq语言编写这样的循环?

假设继承层次结构不包含循环,就像示例中的情况一样,我们可以将问题分解为如下所示的部分:

# Use an inner function of arity 0 to take advantage of jq's TCO
def inherits_from($dict):
  def from:
    if .name == "default" then .
    else $dict[.inherits] as $next
    | ., ($next | from)
    end;
  from;

def chain($start):
  INDEX(.configurePresets[]; .name) as $dict
  | $dict[$start] | inherits_from($dict);

reduce chain("arm") as $x (null;
  ($x.cacheVariables + .cacheVariables) as $cv
  | $x + .
  | .cacheVariables = $cv)
| del(.inherits)

这会有效地产生所需的输出。

上述解决方案的一个优点是可以很容易地对其进行修改以处理循环依赖。

使用recurse/1

inherits_from/1 也可以使用内置函数 recurse/1:

来定义
def inherits_from($dict):
  recurse( select(.name != "default") | $dict[.inherits]) ;

或者更有趣的是:

def inherits_from($dict):
  recurse( select(.inherits) | $dict[.inherits]) ;

使用*

使用 * 组合对象会产生很高的开销,因为它具有递归语义,通常不需要或不需要。然而, 如果这里可以接受使用 * 来组合对象,上面可以简化为:

def inherits_from($dict):
  recurse( select(.inherits) | $dict[.inherits]) ;

INDEX(.configurePresets[]; .name) as $dict
| $dict["arm"] 
| reduce inherits_from($dict) as $x ({};  $x * .)
| del(.inherits)

写一个递归函数其实很简单,一旦你掌握了它:

jq --arg name "" '
    def _get_in(input; n):
        (input[] | select(.name == n)) |
        (if .inherits then .inherits as $n | _get_in(input; $n) else {} end) * .;
    def get(name):
        .configurePresets as $input | _get_in($input; name);
    get($name)
' "$presetfile"

首先我只过滤 .configurePresets 然后在一个函数中我只得到 input[] | select(.name == n) 我感兴趣的部分。然后 if .inherits 如果它有继承,然后 .inherits as $n | _get_in(input; $n)取名字 in inherits 并再次调用自己。否则 return else {} end 为空。然后就是 * .input[] | select(.name == n) 的结果 - 本身合并。所以它递归加载所有 {} * (input[]|select()) * (input[]|select()) * (input[]|select()).