Как рекурсивно использовать jq для вложенного JSON, где каждый объект имеет свойство name?

У меня есть вложенный объект JSON, где каждый уровень имеет один и тот же ключ свойства, и что отличает каждый уровень, это свойство называется name, Если я хочу пройти до уровня, который имеет определенный "путь" name свойства, как бы я сформулировал jq фильтр?

Вот некоторые примеры данных JSON, которые представляют структуру каталогов файловой системы:

{
  "subs": [
    {
      "name": "aaa",
      "subs": [
        {
          "name": "bbb",
          "subs": [
            {
              "name": "ccc",
              "subs": [
                {
                  "name": "ddd",
                  "payload": "xyz"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Что за jq фильтр для получения значения полезной нагрузки в "пути" aaa/bbb/ccc/ddd?

Предыдущие исследования:

  1. jq - выбирать объекты с заданным именем ключа - полезно, но ищет любой элемент в JSON, который содержит указанное имя, тогда как я ищу элемент, который вложен в набор объектов, которые также имеют определенные имена.

  2. http://arjanvandergaag.nl/blog/wrestling-json-with-jq.html - полезно в разделе 4, где показано, как извлечь объект, имеющий свойство name имеющий определенную ценность. Однако выполняемая рекурсия основана на определенном известном наборе имен свойств ("values ​​[]. Links.clone[]"). В моем случае мой эквивалент - это просто "sub []. Sub []. Subs[]".

2 ответа

Вот основа для общего решения:

def descend(name): .subs[] | select(.name == name);

Таким образом, ваш конкретный запрос может быть сформулирован следующим образом:

descend( "aaa") | descend( "bbb") | descend( "ccc") | descend( "ddd") | .payload

Или немного лучше, все еще используя приведенное выше определение descend:

def path(array): 
  if (array|length)==0 then . 
  else descend(array[0]) | path(array[1:])
  end;

path( ["aaa", "bbb", "ccc", "ddd"] ) | .payload

TCO

Вышеуказанное рекурсивное определение path/1 достаточно прост, но не подходит для очень глубоко вложенных структур данных, например, если глубина больше 1000. Вот альтернативное определение, которое использует преимущества оптимизации хвостового вызова jq и поэтому выполняется очень быстро:

def atpath(array):
  [array, .] 
  |  until( .[0] == []; .[0] as $a | .[1] | descend($a[0]) | [$a[1:], . ] )
  | .[1];

.aaa.bbb.ccc.ddd

Если вы хотите использовать нотацию.aaa.bbb.ccc.ddd, один из подходов состоит в том, чтобы начать с "выравнивания" данных:

def flat:
  { (.name): (if .subs then (.subs[] | flat) else .payload end) };

Поскольку элемент верхнего уровня не имеет тега "name", запрос будет:

.subs[] | flat | .aaa.bbb.ccc.ddd

Вот более эффективный подход, снова используя descend определено выше:

def payload(p):
  def get($array):
    if $array == []
    then .payload
    else descend($array[0]) | get($array[1:]) end;
  get( null | path(p) );

payload( .aaa.bbb.ccc.ddd )

Фильтр в следующем jq Команда повторяет "путь" объектов, которые имеют name свойства, которые соответствуют "пути" aaa/bbb/ccc/ddd:

jq '.subs[] | select(.name = "aaa") | .subs[] | select(.name = "bbb") | .subs[] | select(.name = "ccc") | .subs[] | .payload'

Вот это в прямом эфире на qplay.org:

https://jqplay.org/s/tblW7UX0Si

Другие вопросы по тегам