替换 jq 中没有确切路径的子项

Replace subkey without exact path in jq

示例 JSON 文件:

{
  "u": "stuff",
  "x": [1,2,3],
  "y": {
    "field": "value"
  },
  "z": {
    "zz": {
       "name": "change me",
       "more": "stuff"
    },
    "randomKey":  {
       "name": "change me",
       "random": "more stuff"
    }
  }
}

如何将所有名称字段更新为 "something",同时保持 JSON 文件的其余部分不变?

{
  "u": "stuff",
  "x": [1,2,3],
  "y": {
    "field": "value"
  },
  "z": {
    "zz": {
       "name": "something",
       "more": "stuff"
    },
    "randomKey":  {
       "name": "something",
       "random": "more stuff"
    }
  }
}

使用直接路径,这会很容易,但父键(在这种情况下为 z 和 randomKey)会有所不同。

我试过类似的方法:

jq '.z | .. | .name? |= "something"' file.json

它正在更新名称,但也放入所有递归的东西..

如果在任何地方更改 "name" 字段是可以接受的,您可以使用 walk/1:

walk(if type == "object" and has("name") then .name = "something" else . end)

请注意,walk/1 仅在 jq 1.5 发布后才包含在 jq 中。如果你的jq没有,那么你可以在jq FAQ上找到它的定义,例如

如果您只想修改 "z" 上下文中的 "name" 字段,请考虑:

.z |= with_entries(if .value.name?
                   then .value.name = "something" 
                   else . end)

假设 z 中的每个值都有一个名称 属性,您可以这样做:

$ jq --arg newname 'something' '.z[].name = $newname' input.json

对对象使用 [] 将产生该对象中包含的所有值。对于这些值中的每一个,我们只是将 name 设置为新名称。


如果您需要对要更新的内容更具选择性,则必须为要更新的对象添加更多条件。一般来说,我会使用 peak 的方法,但这里有另一种方法可以使用类似于第一种方法的结构来实现,假设我们只想更新已经有 name 属性 的对象:

$ jq --arg newname 'something' '(.z[] | select(has("name")).name) = $newname' input.json

将赋值的 LHS 括在括号中很重要,我们不想在赋值之前更改上下文,否则我们将看不到其余结果。