Jak filtrować tablicę obiektów na podstawie wartości w wewnętrznej tablicy za pomocą jq?

239

Biorąc pod uwagę ten wkład:

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

Próbuję zbudować filtr za pomocą jq, który zwraca wszystkie obiekty zs , Idktóre nie zawierają „danych” w wewnętrznej Namestablicy, z wyjściem oddzielonym znakiem nowej linii. W przypadku powyższych danych chciałbym uzyskać wynik

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19

Myślę, że jestem nieco blisko tego:

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

ale selectfiltr nie jest poprawny i nie kompiluje się (get error: syntax error, unexpected IDENT).

Abe Voelker
źródło

Odpowiedzi:

372

Bardzo blisko! W swoim selectwyrażeniu musisz wcześniej użyć potoku ( |) contains.

Ten filtr generuje oczekiwany wynik.

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

Jq Cookbook ma przykład składni.

Filtruj obiekty na podstawie zawartości klucza

Na przykład chcę tylko obiektów, których klucz gatunku zawiera „dom”.

$ 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 pyta, jak zachować strukturę JSON tablicy, aby końcowym wynikiem była pojedyncza tablica JSON, a nie strumień obiektów JSON.

Najprostszym sposobem jest zawinięcie całego wyrażenia w konstruktor tablicowy:

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

Możesz także użyć funkcji mapy:

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

map rozpakowuje tablicę wejściową, stosuje filtr do każdego elementu i tworzy nową tablicę. Innymi słowy, map(f)jest równoważne z [.[]|f].

Iain Samuel McLean Elder
źródło
Dzięki, działa świetnie! Tak naprawdę widziałem ten przykład, po prostu nie udało mi się go dostosować do mojego scenariusza :-)
Abe Voelker,
1
Czy w ogóle istnieje „zachowanie struktury json tablicy”? Podoba mi się przykład gatunku, ale generuje on dwie „linie jsona”. Nie mogłem zrozumieć części mapy koniecznie
Colin D,
4
@ CololinD Nie byłem naprawdę zadowolony z rozwiązania redukującego, więc zastąpiłem je objaśnieniem funkcji mapy. To pomaga?
Iain Samuel McLean Starszy
@IdentElder - Co się stanie, gdy część wyszukiwanego terminu (w tym przypadku dom) jest zmienną? Powiedzmy, używając --args term se. Zawiera więc („hou $ term”)
SnazzyBootMan
@Chris Zmienna $termbyłaby traktowana jako ciąg, więc powinieneś użyć konkatenacji ciągów:contains("hou" + $term)
Iain Samuel McLean Starszy
17

Oto inne rozwiązanie wykorzystujące dowolne / 2

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

z przykładowymi danymi i -rtworzoną opcją

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19
jq170727
źródło
Dokładnie tego szukałem - dlaczego to działa z średnikiem, .Names[] ; contains()a nie z rurą .Names[] | contains()?
Matt
3
Ach, to jest any(generator; condition)forma. Przekonałem się, że bez użycia any()skończyłyby się z duplikatami w moich wynikach, jeśli zostaną select()dopasowane więcej niż raz na tym samym obiekcie.
Matt