Comment filtrer un tableau d'objets en fonction des valeurs d'un tableau interne avec jq?

240

Compte tenu de cette entrée:

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

J'essaie de construire un filtre avec jq qui retourne tous les objets avec Ids qui ne contiennent pas de "données" dans le Namestableau interne , la sortie étant séparée par des sauts de ligne. Pour les données ci-dessus, la sortie que j'aimerais est

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19

Je pense que je suis un peu proche de ceci:

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

mais le selectfiltre n'est pas correct et il ne compile pas (get error: syntax error, unexpected IDENT).

Abe Voelker
la source

Réponses:

372

Très proche! Dans votre selectexpression, vous devez utiliser un pipe ( |) avant contains.

Ce filtre produit la sortie attendue.

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

Le livre de recettes jq contient un exemple de la syntaxe.

Filtrer les objets en fonction du contenu d'une clé

Par exemple, je veux uniquement des objets dont la clé de genre contient "maison".

$ 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 demande comment conserver la structure JSON du tableau, de sorte que la sortie finale soit un tableau JSON unique plutôt qu'un flux d'objets JSON.

La façon la plus simple consiste à envelopper l'expression entière dans un constructeur de tableau:

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

Vous pouvez également utiliser la fonction de carte:

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

map décompresse le tableau d'entrée, applique le filtre à chaque élément et crée un nouveau tableau. En d'autres termes, map(f)est équivalent à [.[]|f].

Iain Samuel McLean Elder
la source
Merci, fonctionne très bien! J'ai effectivement vu cet exemple, j'ai juste échoué à l'adapter à mon scénario :-)
Abe Voelker
1
Est-il possible de "conserver la structure json du tableau"? J'aime l'exemple de genre mais il produit deux "lignes json". Je ne pouvais pas nécessairement comprendre la partie de la carte
Colin D
4
@ColinD Je n'étais pas vraiment satisfait de la solution de réduction, je l'ai donc remplacée par une explication de la fonction de carte. Est ce que ça aide?
Iain Samuel McLean Elder
@IainElder - Que se passe-t-il lorsque la partie du terme de recherche (dans ce cas, house) est une variable? Donc, utilisez le terme --args se. Contient donc ("hou $ term")
SnazzyBootMan
@Chris La variable $termserait traitée comme une chaîne, vous devez donc utiliser la concaténation de chaînes:contains("hou" + $term)
Iain Samuel McLean Elder
17

Voici une autre solution qui utilise any / 2

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

avec les données d'exemple et l' -roption qu'il produit

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19
jq170727
la source
Exactement ce que je cherchais - pourquoi cela fonctionne-t-il avec un point-virgule .Names[] ; contains()et non avec une pipe .Names[] | contains()?
Matt
3
Ah, c'est la any(generator; condition)forme. J'ai trouvé que sans utiliser, any()je me retrouverais avec des doublons dans mes résultats s'ils select()correspondaient plus d'une fois sur le même objet.
Matt