首页 > 解决方案 > jq:递归重命名键

问题描述

我的 JSON 结构如下所示。“groups”可以嵌套在“items”下任意深度,所以我需要编写一个表达式来访问每个“groups”和“items”属性(或父对象);静态路径不行。

{
    "groups": [{
        "name": "group1",
        "items": [
            {
                "name": "item1",
                "groups": [
                    {
                        "name": "nestedGroup1",
                        "items": [
                            {
                                "name": "nestedItem1",
                                "groups": []
                            },{
                                "name": "nestedItem2",
                                "groups": []
                        }]
                    }
                ]
            },
            {
                "name": "item2",
                "groups": []
            }
        ]
    }]
}

如何递归重命名所有“组”以将“newGroups”和“items”重命名为“newItems”?如果您将其分解,我知道我需要在每个级别创建新键并删除旧键,但我无法表达。我已经尝试了几种变化,例如

.. |= if type == "object" and has("groups") then . + { newGroups: .groups } else . end

执行第一步,但这会导致将顶级“groups”值复制到“newGroups”,同时保持嵌套“groups”不变。然后,“组”中的剩余值也得到同级“newGroups”及其嵌套“组”,没有“newGroups”同级等。

从观察来看,我的理解是算子生成的路径..是先求值的,所以新添加的key永远不会被遍历。因此,我尝试了上述的变体,用..recurse参数和不带参数的方式进行替换,认为它将获取 RHS 产生的值并将其反馈给 LHS,如下所示:

{ newGroups: .groups } | recurse |= if type == "object" and has("groups") then . + { newGroups: .groups } elif type == "object" and has("items") then . + { newItems: .items } else . end

结果更近了,但还是那么远。递归类型的作品,但仅限于已经存在的键;不会访问附加到对象的新对象。

我也尝试过弄乱paths, getpath, 并setpath尝试以这种方式解决问题,但在那里也遇到了问题。请帮我解开!

标签: jq

解决方案


这在jq-FAQ中递归遍历使用中得到了很好的解释walk/1。我使用了 jq-builtins 页面的标准实现walk/1因为我拥有的版本默认情况下没有它。

从文档中walk/1- 递归遍历输入 JSON 实体,将每个遇到的键 , 更改k(k|filter),其中filter是指定的过滤器,它可以是任何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;

def translate_keys(f):
  walk( if type == "object" then
            with_entries( if .key | contains("groups") then
                             .key |= "newgroups"
                          elif .key | contains("items") then
                             .key |= "newitems"
                          else
                             . end
                        )
        else
            . end
      );


translate_keys(.)

并将脚本运行为

jq -f script.jq json

这产生了您需要的结果。


推荐阅读