¿Cómo filtrar una matriz de objetos basados ​​en valores en una matriz interna con jq?

240

Dada esta entrada:

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

Estoy tratando de construir un filtro con jq que devuelva todos los objetos con Ids que no contienen "datos" en la Namesmatriz interna , con la salida separada por una nueva línea. Para los datos anteriores, el resultado que me gustaría es

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19

Creo que estoy algo cerca de esto:

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

pero el selectfiltro no es correcto y no se compila (get error: syntax error, unexpected IDENT).

Abe Voelker
fuente

Respuestas:

372

¡Muy cerca! En tu selectexpresión, tienes que usar un pipe ( |) antes contains.

Este filtro produce la salida esperada.

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

El jq Cookbook tiene un ejemplo de sintaxis.

Filtrar objetos según el contenido de una clave

Por ejemplo, solo quiero objetos cuya clave de género contenga "casa".

$ 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 pregunta cómo preservar la estructura JSON de la matriz, de modo que el resultado final sea una sola matriz JSON en lugar de una secuencia de objetos JSON.

La forma más simple es envolver la expresión completa en un constructor de matrices:

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

También puede usar la función de mapa:

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

map desempaqueta la matriz de entrada, aplica el filtro a cada elemento y crea una nueva matriz. En otras palabras, map(f)es equivalente a [.[]|f].

Iain Samuel McLean Anciano
fuente
Gracias, funciona muy bien! En realidad vi ese ejemplo, simplemente no pude adaptarlo a mi escenario :-)
Abe Voelker
1
¿Hay alguna forma de "preservar la estructura json de la matriz"? Me gusta el ejemplo de género pero genera dos "líneas json". No pude entender la parte del mapa necesariamente
Colin D
44
@ColinD No estaba muy contento con la solución de reducción, así que la reemplacé con una explicación de la función de mapa. ¿Eso ayuda?
Iain Samuel McLean Elder
@IainElder: ¿qué sucede cuando la parte del término de búsqueda (en este caso casa) es una variable? Así que digamos usando --args término se. Así que contiene ("hou $ term")
SnazzyBootMan
@Chris La variable se $termtrataría como una cadena, por lo que debe usar la concatenación de cadenas:contains("hou" + $term)
Iain Samuel McLean Elder
17

Aquí hay otra solución que usa any / 2

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

con los datos de muestra y la -ropción que produce

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19
jq170727
fuente
Exactamente lo que estaba buscando: ¿por qué funciona esto con un punto y coma .Names[] ; contains()y no con una tubería .Names[] | contains()?
Matt
3
Ah, es la any(generator; condition)forma. Descubrí que, sin usar any(), terminaría con duplicados en mis resultados si select()coincidía más de una vez en el mismo objeto.
Matt