繁体   English   中英

jq 1.5 - 如果不存在,则更改现有元素或添加新元素

[英]jq 1.5 - Change existing element or add new if does not exist

使用:

目标和条件:

  1. 将子对象值替换为具有父对象或数组的任意深度的另一个值,例如:
    • 如果 .spec.template.spec.containers[ n ].env[ n ] .name == "CHANGEME" 然后
    • .spec.template.spec.containers[ n ].env[ n ] .value = "xx"
    • 其中n >=0
  2. 如果.name 的任何父项不存在,应该能够即时添加它们而不是退出并出现错误
  3. 输出 JSON 至少应具有与输入 JSON 相同的元素,不应丢失任何现有元素
  4. 数组元素内不允许重复,但必须保持顺序,因此不能使用像unique这样的函数

示例输入 JSON:

结构实际上是强加的,所以我必须服从它。 对象“路径”通常类似于: .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"
                }
              ]
            }
          ]
        }
      }
    }
  }
]

场景

  1. 在保留输入结构的同时替换现有值,按预期工作:
    • jq -r 'map({name:"CHANGEME",value: "xx"} as $v | (.spec.template.spec.containers[].env[] | select(.name==$v.name))|=$v)'
  2. 让我们假设我想做同样的事情,只有 .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":"cassandra","env" 的元素 "env1" 附近的无效路径表达式..
        • 如果使用.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)'
        • 这按预期工作,但是太长太重,必须在对象层次结构中的每个潜在数组之后添加自定义函数

问题

有没有办法简化这一切,让它更通用一点来处理任意数量的父级、数组?

谢谢你。

“问题”

回答这个问题:是的。 jq 1.5 具有keys_unsorted ,因此您可以使用walk/1的以下定义,它现在是 jq 的“主”版本中的标准:

# 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等。

“数组元素内不允许重复”

这可以使用index/1轻松完成; 您可能喜欢使用辅助函数,例如:

def ensure_has($x): if index([$x]) then . else . + [$x] end;

“如果 .name 的任何父级不存在,应该能够即时添加它们”

如果我正确理解了这个要求,那么知道 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

设法达到一个非常好的形式:

  1. ~/.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));
  2. 典型的过滤器如下所示:

     jq -r '{name:"CHANGEME",value: "xx"} as $v | map( when(.kind == "StatefulSet"; .spec.template.spec.containers|arr|.env|arr(.name==$v.name)) |= $v)'

结果将是所有不存在的对象都将被创建。 这里的约定是对每个要成为数组的对象使用arr函数,最后使用布尔条件和对象来替换匹配的对象或添加到父数组(如果不匹配)。

  1. 如果您知道路径始终存在,并且要更新的对象也是如此,则walk更优雅:

     jq -r 'map(updobj(select(.name=="CHANGEME").value|="xx"))'

感谢@peak 的努力和启发解决方案。

暂无
暂无

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

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