jq 1.5 - 更改现有元素或添加新元素(如果不存在)
jq 1.5 - Change existing element or add new if does not exist
使用:
- jq-1.5-1-a5b5cbe (Ubuntu 17.10)
- 撰写查询:https://jqplay.org/
目标和条件:
- 用另一个值替换一个子对象值,在任何深度,具有父对象或数组,例如:
- if .spec.template.spec.containers[n].env[n].name == "CHANGEME" 然后
- .spec.template.spec.containers[n].env[n].value = "xx"
- 其中 n >=0
- 如果 .name 的任何父项不存在,应该能够即时添加它们而不是因错误退出
- 输出 JSON 应至少具有与输入 JSON 相同的元素,不应丢失现有元素
- 数组元素内不允许有重复项,但是必须保留顺序,所以不能使用像
unique
这样的函数
样本输入JSON:
结构其实是强加的,我只好服从。对象 "path" 通常类似于:.spec.template.spec.containers[0].spec.env[1].name
。您还可以有 .containers[1],等等。这是高度可变的,有时某些元素 可能 存在与否,取决于特定 JSON.
的模式定义
[
{
"kind": "StatefulSet",
"spec": {
"serviceName": "cassandra",
"template": {
"spec": {
"containers": [
{
"name": "cassandra",
"env": [
{
"name": "CASSANDRA_SEEDS",
"value": "cassandra-0.cassandra.kong.svc.cluster.local"
},
{
"name": "CHANGEME",
"value": "K8"
}
]
}
]
}
}
}
}
]
场景
- 在保留输入结构的同时替换现有值,按预期工作:
jq -r 'map({name:"CHANGEME",value: "xx"} as $v | (.spec.template.spec.containers[].env[] | select(.name==$v.name))|=$v)'
假设我想做同样的事情,只是 .env1 是对象 {name:"",value:""} 的父数组。预期输出应为:
[
{
"kind": "StatefulSet",
"spec": {
"serviceName": "cassandra",
"template": {
"spec": {
"containers": [
{
"name": "cassandra",
"env": [
{
"name": "CASSANDRA_SEEDS",
"value": "cassandra-0.cassandra.kong.svc.cluster.local"
},
{
"name": "CHANGEME",
"value": "K8"
}
],
"env1": [
{
"name": "CHANGEME",
"value": "xx"
}
]
}
]
}
}
}
}
]
- 为此,我尝试动态添加对象 env1:
jq -r 'map({name:"CHANGEME",value: "xx"} as $v | (.spec.template.spec.containers[] | if .env1 == null then .+={env1:[$v]} | .env1 else .env1 end | .[] | select(.name==$v.name))|=$v)'
- 如果 .env1 存在则有效,否则:
- 错误:试图访问 {"name" 的元素 "env1" 附近的无效路径表达式:"cassandra","env"..
- 如果使用
.env//[$v]
或 .env//=.env[$v]
等符号,结果相同
jq -r 'map({name:"CHANGEME",value: "xx"} as $v | (.spec.template.spec.containers[].env1 | .[if length<0 then 0 else length end]) |= $v)'
- 如果 .env1 不 存在
,则有效
- 如果数组 .env1 存在,则添加另一个元素,可能会重复对象
- 最终我成功地创建了一个有效的过滤器:
jq -r 'def defarr: if length<=0 then .[0] else .[] end; def defarr(item): if length<=0 then .[0] else foreach .[] as $item ([]; if $item.name == item then $item else empty end; .) end; map({name:"CHANGEME",value: "xx"} as $v | (.spec.template.spec | .containers1 | defarr | .env1 | defarr($v.name) ) |=$v)'
- 这按预期工作,但是太长太重,必须在对象层次结构中的每个潜在数组之后添加自定义函数
问题
有什么方法可以简化这一切,让它更通用一点来处理任意数量的父级,数组与否?
谢谢。
"The question"
回答问题:是的。 jq 1.5 有 keys_unsorted
,所以你可以使用下面的 walk/1
的定义,它现在是 jq 的“master”版本的标准:
# Apply f to composite entities recursively, and to atoms
def walk(f):
. as $in
| if type == "object" then
reduce keys_unsorted[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
有关更多详细信息和示例,请参阅 jq 手册的“开发”版本、jq FAQ https://github.com/stedolan/jq/wiki/FAQ 等
"No duplicates are allowed within the elements of an array"
这很容易使用 index/1
完成;您可能喜欢使用辅助函数,例如:
def ensure_has($x): if index([$x]) then . else . + [$x] end;
"If any of the parents of .name do not exist, should be able to add them on the fly"
如果我对这个要求的理解正确,那么知道 jq 将根据赋值创建对象会对您有所帮助,例如
{} | .a.b.c = 1
产量
{"a":{"b":{"c":1}}}
因此,使用您的示例,您可能希望在 walk
:
中包含类似的内容
if type == "object" and has("spec")
then (.spec.template.spec.containers? // null) as $existing
| if $existing then .spec.template.spec.containers |= ...
else .spec.template.spec.containers = ...
end
else .
end
设法达到了很好的状态:
在~/.jq
中添加了以下功能:
def arr:
if length<=0 then .[0] else .[] end;
def arr(f):
if length<=0 then
.[0]
else
.[]|select(f)
end//.[length];
def when(COND; ACTION):
if COND? // null then ACTION else . end;
# Apply f to composite entities recursively, and to atoms
def walk(f):
. as $in
| if type == "object" then
reduce keys_unsorted[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
def updobj(f):
walk(when(type=="object"; f));
典型的过滤器如下所示:
jq -r '{name:"CHANGEME",value: "xx"} as $v |
map( when(.kind == "StatefulSet";
.spec.template.spec.containers|arr|.env|arr(.name==$v.name)) |= $v)'
结果将创建所有不存在的对象。这里的约定是对每个你想成为数组的对象使用arr
函数,最后使用一个布尔条件和一个对象来替换匹配的或者添加到父数组,如果不匹配.
如果你知道路径总是存在的,你想要更新的对象也是如此,walk
更优雅:
jq -r 'map(updobj(select(.name=="CHANGEME").value|="xx"))'
感谢@peak 的努力和对解决方案的启发。
使用:
- jq-1.5-1-a5b5cbe (Ubuntu 17.10)
- 撰写查询:https://jqplay.org/
目标和条件:
- 用另一个值替换一个子对象值,在任何深度,具有父对象或数组,例如:
- if .spec.template.spec.containers[n].env[n].name == "CHANGEME" 然后
- .spec.template.spec.containers[n].env[n].value = "xx"
- 其中 n >=0
- 如果 .name 的任何父项不存在,应该能够即时添加它们而不是因错误退出
- 输出 JSON 应至少具有与输入 JSON 相同的元素,不应丢失现有元素
- 数组元素内不允许有重复项,但是必须保留顺序,所以不能使用像
unique
这样的函数
样本输入JSON:
结构其实是强加的,我只好服从。对象 "path" 通常类似于:.spec.template.spec.containers[0].spec.env[1].name
。您还可以有 .containers[1],等等。这是高度可变的,有时某些元素 可能 存在与否,取决于特定 JSON.
[
{
"kind": "StatefulSet",
"spec": {
"serviceName": "cassandra",
"template": {
"spec": {
"containers": [
{
"name": "cassandra",
"env": [
{
"name": "CASSANDRA_SEEDS",
"value": "cassandra-0.cassandra.kong.svc.cluster.local"
},
{
"name": "CHANGEME",
"value": "K8"
}
]
}
]
}
}
}
}
]
场景
- 在保留输入结构的同时替换现有值,按预期工作:
jq -r 'map({name:"CHANGEME",value: "xx"} as $v | (.spec.template.spec.containers[].env[] | select(.name==$v.name))|=$v)'
假设我想做同样的事情,只是 .env1 是对象 {name:"",value:""} 的父数组。预期输出应为:
[ { "kind": "StatefulSet", "spec": { "serviceName": "cassandra", "template": { "spec": { "containers": [ { "name": "cassandra", "env": [ { "name": "CASSANDRA_SEEDS", "value": "cassandra-0.cassandra.kong.svc.cluster.local" }, { "name": "CHANGEME", "value": "K8" } ], "env1": [ { "name": "CHANGEME", "value": "xx" } ] } ] } } } } ]
- 为此,我尝试动态添加对象 env1:
jq -r 'map({name:"CHANGEME",value: "xx"} as $v | (.spec.template.spec.containers[] | if .env1 == null then .+={env1:[$v]} | .env1 else .env1 end | .[] | select(.name==$v.name))|=$v)'
- 如果 .env1 存在则有效,否则:
- 错误:试图访问 {"name" 的元素 "env1" 附近的无效路径表达式:"cassandra","env"..
- 如果使用
.env//[$v]
或.env//=.env[$v]
等符号,结果相同
jq -r 'map({name:"CHANGEME",value: "xx"} as $v | (.spec.template.spec.containers[].env1 | .[if length<0 then 0 else length end]) |= $v)'
- 如果 .env1 不 存在 ,则有效
- 如果数组 .env1 存在,则添加另一个元素,可能会重复对象
- 最终我成功地创建了一个有效的过滤器:
jq -r 'def defarr: if length<=0 then .[0] else .[] end; def defarr(item): if length<=0 then .[0] else foreach .[] as $item ([]; if $item.name == item then $item else empty end; .) end; map({name:"CHANGEME",value: "xx"} as $v | (.spec.template.spec | .containers1 | defarr | .env1 | defarr($v.name) ) |=$v)'
- 这按预期工作,但是太长太重,必须在对象层次结构中的每个潜在数组之后添加自定义函数
- 为此,我尝试动态添加对象 env1:
问题
有什么方法可以简化这一切,让它更通用一点来处理任意数量的父级,数组与否?
谢谢。
"The question"
回答问题:是的。 jq 1.5 有 keys_unsorted
,所以你可以使用下面的 walk/1
的定义,它现在是 jq 的“master”版本的标准:
# Apply f to composite entities recursively, and to atoms
def walk(f):
. as $in
| if type == "object" then
reduce keys_unsorted[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
有关更多详细信息和示例,请参阅 jq 手册的“开发”版本、jq FAQ https://github.com/stedolan/jq/wiki/FAQ 等
"No duplicates are allowed within the elements of an array"
这很容易使用 index/1
完成;您可能喜欢使用辅助函数,例如:
def ensure_has($x): if index([$x]) then . else . + [$x] end;
"If any of the parents of .name do not exist, should be able to add them on the fly"
如果我对这个要求的理解正确,那么知道 jq 将根据赋值创建对象会对您有所帮助,例如
{} | .a.b.c = 1
产量
{"a":{"b":{"c":1}}}
因此,使用您的示例,您可能希望在 walk
:
if type == "object" and has("spec")
then (.spec.template.spec.containers? // null) as $existing
| if $existing then .spec.template.spec.containers |= ...
else .spec.template.spec.containers = ...
end
else .
end
设法达到了很好的状态:
在
~/.jq
中添加了以下功能:def arr: if length<=0 then .[0] else .[] end; def arr(f): if length<=0 then .[0] else .[]|select(f) end//.[length]; def when(COND; ACTION): if COND? // null then ACTION else . end; # Apply f to composite entities recursively, and to atoms def walk(f): . as $in | if type == "object" then reduce keys_unsorted[] as $key ( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f elif type == "array" then map( walk(f) ) | f else f end; def updobj(f): walk(when(type=="object"; f));
典型的过滤器如下所示:
jq -r '{name:"CHANGEME",value: "xx"} as $v | map( when(.kind == "StatefulSet"; .spec.template.spec.containers|arr|.env|arr(.name==$v.name)) |= $v)'
结果将创建所有不存在的对象。这里的约定是对每个你想成为数组的对象使用arr
函数,最后使用一个布尔条件和一个对象来替换匹配的或者添加到父数组,如果不匹配.
如果你知道路径总是存在的,你想要更新的对象也是如此,
walk
更优雅:jq -r 'map(updobj(select(.name=="CHANGEME").value|="xx"))'
感谢@peak 的努力和对解决方案的启发。