jq: рекурсивно переименовывать ключи - PullRequest
0 голосов
/ 29 июня 2018

У меня есть JSON со структурой, как показано ниже. «groups» могут быть вложены в «items» на любую произвольную глубину, поэтому мне нужно написать выражение, которое посещает каждое свойство «groups» и «items» (или родительский объект); статические пути не подойдут.

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

Как я могу рекурсивно переименовать все "группы", чтобы сказать "новые группы" и "элементы" в "новые элементы"? Если разбить его, я знаю, что мне нужно создавать новые ключи на каждом уровне и удалять старые, но у меня возникают проблемы с выражением этого. Я пробовал несколько вариантов таких вещей, как

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

для выполнения первого шага, но это приводит к копированию значения «groups» верхнего уровня в «newGroups», оставляя вложенные «groups» без изменений. Остальные значения в пределах «групп» также получают одноуровневые «новые группы» с вложенными «группами» без братьев и сестер «новые группы» и т. Д.

Из наблюдений я понимаю, что пути, сгенерированные оператором .., оцениваются первыми, поэтому вновь добавленный ключ никогда не пересекается. Затем я попробовал варианты вышеупомянутого, заменив .. на 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, чтобы попытаться решить проблему таким образом, но также столкнулся с проблемами там. Пожалуйста, помогите мне отклеиться!

Ответы [ 2 ]

0 голосов
/ 01 июля 2018

Если проблема требует, чтобы «элементы» переименовывались только в поддереве с помощью ключа «groups», тогда будет требоваться обход в пределах шага:

walk(if type == "object" and has("groups")
     then .newGroups = .groups | del(.groups)
     | walk(if type == "object" and has("items")
            then .newItems = .items | del(.items)
            else . end)
     else . end)
0 голосов
/ 29 июня 2018

Это очень хорошо объяснено в jq - FAQ при рекурсивном обходе с использованием walk/1. Я использовал стандартную реализацию walk/1 со страницы jq-builtins , поскольку у версии, которая у меня есть, по умолчанию ее нет.

Из документации для 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

, который дал нужный вам результат.

...