Як відфільтрувати масив об'єктів на основі значень у внутрішньому масиві за допомогою jq?


239

Враховуючи цей вхід:

[
  {
    "Id": "cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b",
    "Names": [
      "condescending_jones",
      "loving_hoover"
    ]
  },
  {
    "Id": "186db739b7509eb0114a09e14bcd16bf637019860d23c4fc20e98cbe068b55aa",
    "Names": [
      "foo_data"
    ]
  },
  {
    "Id": "a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19",
    "Names": [
      "jovial_wozniak"
    ]
  },
  {
    "Id": "76b71c496556912012c20dc3cbd37a54a1f05bffad3d5e92466900a003fbb623",
    "Names": [
      "bar_data"
    ]
  }
]

Я намагаюся побудувати фільтр з jq, який повертає всі об'єкти з Ids, які не містять "даних" у внутрішньому Namesмасиві, при цьому вихід відокремлений новим рядком . Для наведених вище даних результат, який я хотів би, є

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19

Я думаю, що я дещо близький до цього:

(. - select(.Names[] contains("data"))) | .[] .Id

але selectфільтр невірний і він не компілюється (отримує error: syntax error, unexpected IDENT).

Відповіді:


372

Дуже близько! У вашому selectвираженні ви повинні використовувати трубу ( |) раніше contains.

Цей фільтр дає очікуваний вихід.

. - map(select(.Names[] | contains ("data"))) | .[] .Id

У кулінарній книзі jq є приклад синтаксису.

Фільтруйте об'єкти на основі вмісту ключа

Наприклад, я хочу лише об'єкти, жанровий ключ яких містить "будинок".

$ json='[{"genre":"deep house"}, {"genre": "progressive house"}, {"genre": "dubstep"}]'
$ echo "$json" | jq -c '.[] | select(.genre | contains("house"))'
{"genre":"deep house"}
{"genre":"progressive house"}

Колін D запитує, як зберегти структуру масиву JSON, щоб кінцевий вихід був єдиним масивом JSON, а не потоком JSON-об'єктів.

Найпростіший спосіб - обернути весь вираз у конструкторі масиву:

$ echo "$json" | jq -c '[ .[] | select( .genre | contains("house")) ]'
[{"genre":"deep house"},{"genre":"progressive house"}]

Ви також можете використовувати функцію карти:

$ echo "$json" | jq -c 'map(select(.genre | contains("house")))'
[{"genre":"deep house"},{"genre":"progressive house"}]

map розпаковує вхідний масив, застосовує фільтр до кожного елемента та створює новий масив. Іншими словами, map(f)рівнозначно [.[]|f].


Дякую, чудово працює! Я насправді бачив цей приклад, я просто не зміг його адаптувати до мого сценарію :-)
Abe Voelker

1
Чи є в будь-якому випадку "збереження структури json масиву"? Мені подобається жанровий приклад, але він видає два "рядки json". Я не зміг визначити частину карти обов’язково
Colin D

4
@ColinD Я не дуже задоволений рішенням скорочення, тому замінив його поясненням функції карти. Чи допомагає це?
Ієн Самуель Маклін Старший

@IainElder - Що відбувається, коли частина пошукового терміну (в даному випадку будинок) є змінною? Так скажімо, використовуючи --args термін se. Так міститься ("hou $ term")
SnazzyBootMan

@Chris Змінна $termтрактуватиметься як рядок, тому вам слід скористатись рядковим конкатенацією:contains("hou" + $term)
Iain Samuel McLean Elder

17

Ось ще одне рішення, в якому використовується будь-який / 2

map(select(any(.Names[]; contains("data"))|not)|.Id)[]

із зразковими даними та -rопцією, яку він створює

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19

Саме те, що я шукав - чому це працює з напівкрапкою, .Names[] ; contains()а не з трубою .Names[] | contains()?
Метт

3
Ах, це any(generator; condition)форма. Я виявив, що без використання any()я б у кінцевому підсумку отримав дублікати у своїх результатах, якщо select()збігався більше одного разу на одному об’єкті.
Метт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.