Будемо вважати, що синтаксис і типи даних JSON нам уже знайомі.
jq працює так: пропускає вхідні дані JSON через єдиний вираз (записаний як конвеєр фільтрів), щоб отримати потрібні перетворені дані.
Мову jq реалізує програма jq.
Ця програма надає кілька зручних параметрів командного рядка, які керують тим, як споживаються вхідні дані й як подаються вихідні дані.
У наведених нижче прикладах ми побачимо:
-n або --null-input
Зазвичай програмі jq дають файл для читання, або ми надсилаємо дані на її вхід.
Параметр --null-input дає змогу генерувати дані JSON без жодних вхідних даних.
-c або --compact-output
Типово jq виводить результат у гарно відформатованому вигляді.
Людям надзвичайно зручно переглядати дані, коли вони гарно відформатовані.
Проте машинам це не потрібно: параметр --compact-output прибирає пробільні символи форматування, щоб зменшити розмір отриманого JSON.
-f filename або --from-file filename
Читати програму jq з filename замість того, щоб передавати її в командному рядку.
sed і awk використовують параметр -f з тією самою метою.
Ми побачимо це в тестових скриптах для практичних вправ.
Далі в цьому уроці ми зосередимося на мові jq.
Фільтри також називають виразами.
Фільтр приймає вхідні дані й дає вихідні.
Як і під час роботи в оболонці Unix, можна зʼєднати фільтри конвеєром |, щоб вихідні дані одного фільтра стали вхідними для іншого.
.
Це найпростіший фільтр. Він просто передає свої вхідні дані як вихідні.
$ echo '[1, 2, 3]' | jq '.'
[
1,
2,
3
]
Це буде короткий вступ до роботи з масивами. Детальніше ми розглянемо цю тему пізніше.
До елементів масиву звертаються через квадратні дужки, а індексація починається з нуля.
$ echo '[10, 20, 30]' | jq '.[1]'
20
Фільтр може побудувати масив, загорнувши вираз у [ та ]
з відомим переліком елементів:
jq -n '[1, 2, 3]'
щоб зібрати потік елементів: наприклад,
range - це функція, яка виводить потік чисел
$ jq -n 'range(10; 70; 15)'
10
25
40
55
Використання [] збирає результати виразу в масив
$ jq -c -n '[range(10; 70; 15)]'
[10,25,40,55]
Кома - це не просто синтаксис, що розділяє елементи масиву. Кома - це оператор, який обʼєднує потоки.
Наприклад, [1, 2, 3] - це фільтр, який використовує конструктор масиву [], щоб зібрати результат обʼєднання трьох виразів 1, 2 і 3.
Звернімо увагу на крапки з комою в range(10; 70; 15) вище?
Оскільки коми в мові jq мають особливе призначення, функції, які приймають кілька аргументів, розділяють їх крапками з комою.
Короткий вступ до обʼєктів.
Як і в багатьох мовах програмування, до властивостей обʼєкта звертаються через крапки
$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"
Дужки можна використовувати і для обʼєктів, але тоді рядки тексту (англ. string) потрібно брати в лапки. Це один зі способів працювати з ключами, що містять пробіли.
$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"
Обʼєкт можна побудувати за допомогою {} і пар key: value.
Лапки не потрібні навколо ключів, які є «простими» рядками тексту.
jq -n '{question: (6 * 9), answer: 42}'
виводить
{
"question": 54,
"answer": 42
}
Щоб трактувати ключ як вираз, його потрібно взяти в круглі дужки (наступний приклад також виводить те саме, що й попередній).
echo '[{"key":"question", "value":54}, {"key":"answer", "value":42}]' \
| jq '{(.[0].key): .[0].value, (.[1].key): .[1].value}'
Наприклад, маємо file.json з таким вмістом
{
"key1": "value1",
"key2": [5, 15, 25]
}
Обчислімо довжину масиву key2:
$ jq '.key2 | length' file.json
3
Ми передаємо конвеєром вихідні дані виразу .key2 як вхідні для length, яка, що не дивно, виводить кількість елементів у масиві.
У цьому прикладі вхідні дані JSON ігноруються й не впливають на вихідні дані:
$ echo '{"answer": 42}' | jq '6 * 9'
54
Фільтр може вивести більше ніж одне значення.
Наприклад, фільтр .[] виводить кожен елемент масиву як окреме значення:
$ jq -n -c '[1, 2, 3]'
[1,2,3]
$ jq -n -c '[1, 2, 3] | .[]'
1
2
3
Якщо передати такий фільтр конвеєром в інший, другий фільтр виконається для кожного значення:
$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6
Це схоже на неявну ітерацію.
Коли ми зрозуміємо цей прийом, то побачимо, що дуже потужні фільтри jq можуть бути дуже лаконічними.
Круглі дужки використовують, щоб обʼєднати підвирази в групу й задати порядок виконання операцій, як і в інших мовах.
У jq потреба в них може видатися дещо несподіваною.
Наприклад, припустімо, ми хочемо побудувати масив з 2 елементів: квадратного кореня з 9 та e у степені 1.
Два окремі вирази - 9 | sqrt і 1 | exp.
Ми очікуємо, що вихідні дані будуть масивом [3, 2.7...]
$ jq -n '[ 9|sqrt, 1|exp ]'
[
20.085536923187668,
2.718281828459045
]
Чому ми не отримали очікуваного? jq розуміє це так:
[ ((9|sqrt), 1) | exp ]
jq будує потік із двох елементів (3 і 1), і кожен із них передається до exp.
Потрібно простежити, щоб exp отримувала лише одне число на вході.
Інакше кажучи, потрібно змусити конвеєр обчислитися раніше за кому.
$ jq -n '[ 9|sqrt, (1|exp) ]'
[
3,
2.718281828459045
]
Не заглиблюючись надто (функції будуть темою іншої вправи), ось кілька корисних вбудованих функцій:
length
Отримуючи масив як вхідні дані, виводить кількість елементів у масиві.
$ jq -n '[10, 20, 30, 40] | length'
4
+
Цей оператор робить різні речі залежно від типу своїх операндів: додає числа, зʼєднує рядки тексту, приєднує масиви, обʼєднує обʼєкти.
$ jq -c -n '
3 + 4,
"foo" + "bar",
["a", "b"] + ["c"],
{"m": 10} + {"n": 20}
'
7
"foobar"
["a","b","c"]
{"m":10,"n":20}
add - це функція, яка приймає масив і повертає елемент, у якому всі елементи додані один до одного за правилами +.
[1, 2, 3] | add виводить 6.
map
Отримуючи масив як вхідні дані та фільтр як аргумент, виводить масив, у якому фільтр застосовано до кожного елемента
$ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
[2,4,6,8]
select
Отримуючи якісь вхідні дані та фільтр як аргумент:
null, а справді нічого)Наприклад, маючи якісь числа, виберімо ті, що діляться на 3
$ jq -n 'range(10) | select(. % 3 == 0)'
0
3
6
9
Пригадаймо, що range виводить потік чисел.
select буде викликатися по одному разу для кожного числа.
Виводяться лише ті числа, які «проходять» вираз.
Часто потрібно вибрати елементи масиву. Є кілька способів це зробити.
Маючи вхідні дані ["Anne", "Bob", "Cathy", "Dave"], виберімо імена довжиною 4.
використаймо map і select разом
map(select(length == 4))
розкладімо масив на елементи, застосуймо select до цього потоку й зберімо результати
[ .[] | select(length == 4) ]
Коментарі починаються із символу # і тривають до кінця рядка.
Нам раптом захотілося млинців. Як сучасна технічно орієнтована людина, ми пишемо список покупок для інгредієнтів млинців у форматі JSON. Структура списку така:
{
"name": "name of shopping list",
"ingredients": [ ...list of ingredients... ],
"optional ingredients": [ ...list of ingredients... ]
}
Інгредієнт представлено як обʼєкт JSON, наприклад:
{
"item": "flour",
"amount": {
"quantity": 2,
"unit": "cup"
}
}
Інгредієнт має необовʼязкову властивість з назвою "substitute", яка містить рядок тексту (англ. string) з назвою іншого продукту, який можна використати замість нього.
Погляньмо, як можна дослідити список покупок за допомогою jq.
Напишіть вираз, який виводить елемент «name» зі списку покупок.
Рахуйте лише «обовʼязкові» інгредієнти. Не включайте необовʼязкові інгредієнти.
Напишіть вираз, який виводить кількість цукру. Потрібна лише числова частина.
Деякі інгредієнти можна замінити (якщо немає інгредієнта X, можна використати Y). Виведіть обʼєкт JSON, який зіставляє рекомендований інгредієнт із його заміною. Включіть необовʼязкові інгредієнти до цього зіставлення.
Зареєструйтеся на Exercism, щоб вивчати й опановувати jq, а також 12 концепцій75 вправ та справжнє наставництво від людей, і все це безкоштовно.