Wie filtere ich ein Array von Objekten basierend auf Werten in einem inneren Array mit jq?

238

Angesichts dieser Eingabe:

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

Ich versuche, mit jq einen Filter zu erstellen , der alle Objekte mit Ids zurückgibt , die keine "Daten" im inneren NamesArray enthalten, wobei die Ausgabe durch Zeilenumbrüche getrennt ist. Für die obigen Daten möchte ich Folgendes ausgeben

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19

Ich denke, ich bin etwas nah dran:

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

Der selectFilter ist jedoch nicht korrekt und wird nicht kompiliert (get error: syntax error, unexpected IDENT).

Abe Voelker
quelle

Antworten:

371

Sehr nah! In Ihrem selectAusdruck müssen Sie zuvor eine pipe ( |) verwendencontains .

Dieser Filter erzeugt die erwartete Ausgabe.

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

Das jq-Kochbuch enthält ein Beispiel für die Syntax.

Filtern Sie Objekte basierend auf dem Inhalt eines Schlüssels

ZB möchte ich nur Objekte, deren Genre-Schlüssel "Haus" enthält.

$ 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. fragt, wie die JSON-Struktur des Arrays beibehalten werden soll, sodass die endgültige Ausgabe ein einzelnes JSON-Array und kein Stream von JSON-Objekten ist.

Am einfachsten ist es, den gesamten Ausdruck in einen Array-Konstruktor zu packen:

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

Sie können auch die Kartenfunktion verwenden:

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

map entpackt das Eingabearray, wendet den Filter auf jedes Element an und erstellt ein neues Array. Mit anderen Worten, map(f)ist äquivalent zu [.[]|f].

Iain Samuel McLean Elder
quelle
Danke, funktioniert super! Ich habe dieses Beispiel tatsächlich gesehen, ich konnte es einfach nicht an mein Szenario anpassen :-)
Abe Voelker
1
Gibt es überhaupt eine Möglichkeit, "die JSON-Struktur des Arrays beizubehalten"? Ich mag das Genre-Beispiel, aber es gibt zwei "json-Zeilen" aus. Ich konnte den Kartenteil nicht unbedingt herausfinden
Colin D
4
@ColinD Ich war mit der Reduktionslösung nicht wirklich zufrieden und habe sie durch eine Erklärung der Kartenfunktion ersetzt. Hilft das?
Iain Samuel McLean Elder
@IainElder - Was passiert, wenn der Teil des Suchbegriffs (in diesem Fall Haus) eine Variable ist? Sagen Sie also mit --args term se. Also enthält ("hou $ term")
SnazzyBootMan
@ Chris Die Variable $termwürde als Zeichenfolge behandelt, daher sollten Sie die Zeichenfolgenverkettung verwenden:contains("hou" + $term)
Iain Samuel McLean Elder
17

Hier ist eine andere Lösung, die any / 2 verwendet

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

mit den Beispieldaten und der -rOption, die es erzeugt

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19
jq170727
quelle
Genau das, wonach ich gesucht habe - warum funktioniert das mit einem Semikolon .Names[] ; contains()und nicht mit einer Pfeife .Names[] | contains()?
Matt
3
Ah, es ist die any(generator; condition)Form. Ich stellte fest, dass ich ohne Verwendung any()Duplikate in meinen Ergebnissen erhalten würde, wennselect() mehr als einmal mit demselben Objekt übereinstimmte.
Matt