#json #command-line #jq
Вопрос:
С помощью jq я хотел бы задать свойство в данных JSON и позволить jq вывести исходный JSON с обновленным значением. Я нашел, более или менее методом проб и ошибок, решение и хочу понять, почему и как оно работает.
У меня есть следующие данные JSON:
{
"notifications": [
{
"source": "observer01",
"channel": "error",
"time": "2021-01-01 01:01:01"
},
{
"source": "observer01",
"channel": "info",
"time": "2021-02-02 02:02:02"
}
]
}
Моя цель состоит в том, чтобы обновить time
свойство объекта с помощью определенного source
и channel
(исходный JSON намного длиннее с большим количеством объектов в notifications
массиве того же формата).
(В следующем примере я хочу обновить time
свойство observer01
with channel info
, поэтому второй объект в приведенных выше данных примера.)
Моей первой попыткой, не приведшей к желаемому результату, была следующая jq
команда:
jq '.notifications[] | select(.source == "observer01" and .channel == "info").time = "NEWTIME"' data.json
Это приводит к следующему результату:
{
"source": "observer01",
"channel": "error",
"time": "2021-01-01 01:01:01"
},
{
"source": "observer01",
"channel": "info",
"time": "NEWTIME"
}
Который представляет собой просто список объектов JSON в notifications
массиве. Я понимаю, что это может быть полезно, например, для передачи объектов в другие инструменты командной строки.
Теперь давайте попробуем следующую jq
команду, которая такая же, как и выше, плюс одна пара скобок:
jq '(.notifications[] | select(.source == "observer01" and .channel == "info").time) = "NEWTIME"' data.json
Это приведет к желаемому результату, исходному действительному JSON с обновленным time
свойством:
{
"notifications": [
{
"source": "observer01",
"channel": "error",
"time": "2021-01-01 01:01:01"
},
{
"source": "observer01",
"channel": "info",
"time": "NEWTIME"
}
]
}
Почему добавление круглых скобок в jq
фильтр в приведенном выше случае приводит к другому результату?
Ответ №1:
Скобки просто меняют приоритет. Это задокументировано в man jq
:
Скобки работают как оператор группировки, как и в любом типичном языке программирования.
jq ´(. 2) * 5´ 1 => 15
Давайте рассмотрим более простой пример:
echo '[{"a":1}, {"a":2}]' | jq '.[] | .a |= . 1'
Это выводит
{
"a": 2
}
{
"a": 3
}
потому что это интерпретируется как
↓ ↓
echo '[{"a":1}, {"a":2}]' | jq '.[] | (.a |= . 1)'
Первый фильтр .[]
выводит элементы как отдельные объекты, затем они изменяются вторым фильтром.
Размещение скобок после первых двух элементов изменяет приоритет:
↓ ↓
echo '[{"a":1}, {"a":2}]' | jq '(.[] | .a) |= . 1'
и производит другой вывод:
[
{
"a": 2
},
{
"a": 3
}
]
Кстати, это тот же вывод, что и из
echo '[{"a":1}, {"a":2}]' | jq '.[].a |= . 1'
Он изменяет значение, связанное с "a"
ключом в массиве.
Комментарии:
1. 1 Большое спасибо за ваше объяснение @choroba, ваши примеры помогли мне понять, какое значение имеют эти скобки.
Ответ №2:
Давайте сравним эти два.
.notifications[] | select(...).time = "NEWTIME"
(.notifications[] | select(...).time) = "NEWTIME"
В первом фильтр верхнего уровня определяется |
. Вход-это объект, а выход-результат применения select(...).time = "NEWTIME"
к каждому полученному значению .notifications[]
. По сути, исходный объект «потерян».
Во втором фильтр верхнего уровня определяется =
. x = y
возвращает свои входные данные в качестве выходных, но с побочным эффектом, создаваемым
- Определение того, к чему относится выражение пути
x
во входных данных, - Оценка фильтра
y
на входе (даже такое выражение, как"NEWTIME"
просто фильтр: то, которое игнорирует его ввод и возвращает строку"NEWTIME"
) - Присвоение результата действия
y
объекту, к которому обращаетсяx
.
Комментарии:
1. 1 Спасибо @chepner! Хотя @choroba также сделал отличный пост, я выбираю ваш в качестве принятого ответа, потому что вы дали несколько действительно полезных подробных объяснений.