Треки
/
jq
jq
/
Вправи
/
Список покупок
Список покупок

Список покупок

Навчальна вправа

Вступ

Основи

Будемо вважати, що синтаксис і типи даних JSON нам уже знайомі.

jq працює так: пропускає вхідні дані JSON через єдиний вираз (записаний як конвеєр фільтрів), щоб отримати потрібні перетворені дані.

Note

Мову 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"
Note

Дужки можна використовувати і для обʼєктів, але тоді рядки тексту (англ. 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.

1. Витягти назву списку покупок

Напишіть вираз, який виводить елемент «name» зі списку покупок.

2. Порахувати, скільки інгредієнтів потрібно

Рахуйте лише «обовʼязкові» інгредієнти. Не включайте необовʼязкові інгредієнти.

3. Скільки цукру?

Напишіть вираз, який виводить кількість цукру. Потрібна лише числова частина.

4. Зіставити інгредієнти, які можна замінити

Деякі інгредієнти можна замінити (якщо немає інгредієнта X, можна використати Y). Виведіть обʼєкт JSON, який зіставляє рекомендований інгредієнт із його заміною. Включіть необовʼязкові інгредієнти до цього зіставлення.

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
jq Exercism

Час розпочати Список покупок?

Зареєструйтеся на Exercism, щоб вивчати й опановувати jq, а також 12 концепцій75 вправ та справжнє наставництво від людей, і все це безкоштовно.