Как рекурсивно использовать 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?
Предыдущие исследования:
jq - выбирать объекты с заданным именем ключа - полезно, но ищет любой элемент в JSON, который содержит указанное имя, тогда как я ищу элемент, который вложен в набор объектов, которые также имеют определенные имена.
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: