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



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

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

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

我可以编写一个我相信有效的 bash shell 循环,借助 从 JSON 对象中删除键:值 使用 jq 和这个答案:

input=arm
# extract one element
g() { jq --arg name "$1" '.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 "%sn" "$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手册并学习变量和循环,但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 "$1" '
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)继承中取名并再次调用自己。否则返回else {} end为空。然后* .input[] | select(.name == n)的结果合并 - 本身。所以它递归加载所有{} * (input[]|select()) * (input[]|select()) * (input[]|select())

最新更新