Bagaimana cara memfilter array objek berdasarkan nilai-nilai dalam array batin dengan jq?

240

Diberikan masukan ini:

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

Saya mencoba untuk membangun filter dengan jq yang mengembalikan semua objek dengan Ids yang tidak mengandung "data" di dalam Namesarray, dengan output dipisahkan oleh baris baru. Untuk data di atas, output yang saya inginkan adalah

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19

Saya pikir saya agak dekat dengan ini:

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

tetapi selectfilter tidak benar dan tidak dikompilasi (dapatkan error: syntax error, unexpected IDENT).

Abe Voelker
sumber

Jawaban:

372

Sangat dekat! Dalam selectekspresi Anda, Anda harus menggunakan pipa ( |) sebelumnya contains.

Filter ini menghasilkan output yang diharapkan.

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

The JQ Cookbook memiliki contoh sintaks.

Memfilter objek berdasarkan konten kunci

Misalnya, saya hanya ingin objek yang kunci genre-nya berisi "rumah".

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

Colin D bertanya bagaimana cara melestarikan struktur JSON dari array, sehingga hasil akhir adalah array JSON tunggal daripada aliran objek JSON.

Cara paling sederhana adalah dengan membungkus seluruh ekspresi dalam konstruktor array:

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

Anda juga dapat menggunakan fungsi peta:

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

map membongkar array input, menerapkan filter ke setiap elemen, dan membuat array baru. Dengan kata lain, map(f)setara dengan [.[]|f].

Iain Samuel McLean Elder
sumber
Terima kasih, berhasil! Saya benar-benar melihat contoh itu, saya gagal beradaptasi dengan skenario saya :-)
Abe Voelker
1
Apakah ada pula untuk "melestarikan struktur json dari array"? Saya suka contoh genre tetapi menghasilkan dua "garis json". Saya tidak dapat menemukan bagian peta dengan baik
Colin D
4
@ColinD Saya tidak begitu suka dengan solusi pengurangan, jadi saya menggantinya dengan penjelasan fungsi peta. Apakah itu membantu?
Iain Samuel McLean Penatua
@IainElder - Apa yang terjadi ketika bagian dari istilah pencarian (dalam kasus ini rumah) adalah variabel? Jadi katakanlah menggunakan --args istilah se. Jadi mengandung ("Anda $ jangka")
SnazzyBootMan
@ Chris Variabel $termakan diperlakukan sebagai string, jadi Anda harus menggunakan penggabungan string:contains("hou" + $term)
Iain Samuel McLean Elder
17

Berikut adalah solusi lain yang menggunakan / 2

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

dengan data sampel dan -ropsi yang dihasilkannya

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19
jq170727
sumber
Persis apa yang saya cari - mengapa ini bekerja dengan titik koma .Names[] ; contains()dan bukan dengan pipa .Names[] | contains()?
Matt
3
Ah, itu any(generator; condition)formulirnya. Saya menemukan bahwa tanpa menggunakan any()saya akan berakhir dengan duplikat dalam hasil saya jika select()cocok lebih dari satu kali pada objek yang sama.
Matt