jq funciona pasando los datos JSON de entrada por una única expresión (escrita como una tubería de filtros) para conseguir los datos transformados que se deseen.
jq
El lenguaje jq lo implementa el programa jq.
Este programa ofrece varias opciones de línea de comandos muy prácticas para controlar cómo se consume la entrada y cómo se presenta la salida.
En los ejemplos siguientes encontrarás:
-n o --null-input
Normalmente al programa jq se le da un fichero que leer, o bien le envías datos a su entrada.
La opción --null-input te permite generar datos JSON sin ninguna entrada.
-c o --compact-output
De forma predeterminada, jq presenta la salida con un formato legible.
Es enormemente útil que las personas puedan ver los datos cuando están bien formateados.
Sin embargo, para las máquinas no es necesario: la opción --compact-output elimina los espacios en blanco del formato para minimizar el tamaño del JSON resultante.
-f filename o --from-file filename
Lee el programa jq desde filename en lugar de proporcionarlo en la línea de comandos.
Tanto sed como awk usan la opción -f para el mismo propósito.
Verás esto en los scripts de test de los ejercicios de práctica.
Consulta el manual para obtener detalles sobre todas las opciones.
Los filtros también se conocen como expresiones.
Un filtro toma una entrada y produce una salida.
Igual que cuando trabajas en un shell de Unix, puedes unir filtros con una tubería | para conectar la salida de un filtro con la entrada de otro.
.
Este es el filtro más sencillo.
Simplemente pasa su entrada a su salida.
Por ejemplo, jq presenta la salida con un formato legible de forma predeterminada, así que pasar JSON al filtro . te da una salida bien formateada sin esfuerzo.
$ echo '[1, 2, 3]' | jq '.'
[
1,
2,
3
]
Esta será una breve introducción al trabajo con arrays. Cubriremos este tema con más detalle más adelante.
Se accede a los elementos de un array con corchetes, y su índice empieza en cero.
$ echo '[10, 20, 30]' | jq '.[1]'
20
Un filtro puede construir un array envolviendo una expresión entre [ y ]
con una lista conocida de elementos:
jq -n '[1, 2, 3]'
para recoger un flujo de elementos: por ejemplo,
range es una función que genera un flujo de números
$ jq -n 'range(10; 70; 15)'
10
25
40
55
Usar [] recoge los resultados de la expresión en un array
$ jq -c -n '[range(10; 70; 15)]'
[10,25,40,55]
La coma no es solo sintaxis que separa elementos de un array. La coma es un operador que une flujos.
Por ejemplo, [1, 2, 3] es un filtro que usa el constructor de array [] para recoger el resultado de unir las tres expresiones 1, 2 y 3.
¿Te has fijado en los puntos y comas de range(10; 70; 15) de arriba?
Como las comas tienen un propósito específico en el lenguaje jq, las funciones que toman varios argumentos usan puntos y comas para separar los argumentos.
Una breve introducción a los objetos.
Como en muchos lenguajes de programación, usa puntos para acceder a las propiedades de un objeto
$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"
También se pueden usar corchetes con los objetos, pero entonces hacen falta comillas para los literales de string. Este es uno de los métodos para trabajar con claves que contienen espacios.
$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"
Puedes construir un objeto con {} y pares key: value.
No hacen falta comillas alrededor de las claves que son strings «simples».
jq -n '{question: (6 * 9), answer: 42}'
genera como salida
{
"question": 54,
"answer": 42
}
Para tratar la clave como una expresión, debes envolverla entre paréntesis (lo siguiente también genera la misma salida que arriba).
echo '[["question", "answer"], [54, 42]]' \
| jq '{(.[0][0]): .[1][0], (.[0][1]): .[1][1]}'
Es muy habitual querer extraer un subconjunto de claves de un objeto grande.
Por ejemplo, para extraer id y name de
{
"id": 101,
"name": "alpha widget",
"specifications": {...}
}
Podríamos escribir
{id: .id, name: .name}
Pero como esto es tan habitual, existe una sintaxis abreviada:
{id, name}
Por ejemplo, dado file.json con el contenido
{
"key1": "value1",
"key2": [5, 15, 25]
}
Vamos a calcular la longitud del array key2:
$ jq '.key2 | length' file.json
3
Estamos enviando por una tubería la salida de la expresión .key2 como entrada a length, que como era de esperar genera como salida el número de elementos del array.
Este es un aspecto de jq al que cuesta un poco acostumbrarse: la mayoría de las funciones (aunque no todas) actúan como filtros, a los que pasas datos por la entrada del filtro, no como argumento.
En este ejemplo, los datos JSON de entrada se ignoran y no influyen en la salida:
$ echo '{"answer": 42}' | jq '6 * 9'
54
Un filtro puede generar más de un valor.
Por ejemplo, el filtro .[] genera cada elemento de un array como un valor independiente:
$ jq -n -c '[1, 2, 3]'
[1,2,3]
$ jq -n -c '[1, 2, 3] | .[]'
1
2
3
Si pasas un filtro así por una tubería hacia otro, se ejecutará el segundo filtro para cada valor:
$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6
Esto es como una iteración implícita.
Cuando entiendas esta técnica, te darás cuenta de que se pueden escribir filtros de jq muy potentes de forma muy concisa.
Los paréntesis se usan para agrupar subexpresiones y forzar así el orden de las operaciones, igual que en otros lenguajes.
En jq, la necesidad de usarlos puede resultar bastante sorprendente.
Por ejemplo, supongamos que queremos construir un array con 2 elementos: la raíz cuadrada de 9; y e elevado a la potencia 1.
Las dos expresiones individuales son 9 | sqrt y 1 | exp.
Esperamos que la salida sea el array [3, 2.7...]
$ jq -n '[ 9|sqrt, 1|exp ]'
[
20.085536923187668,
2.718281828459045
]
¿Por qué no obtuvimos lo que esperábamos? jq lo interpreta así:
[ ((9|sqrt), 1) | exp ]
jq construye un flujo de dos elementos (3 y 1) que se pasan cada uno a exp.
Tenemos que asegurarnos de que exp recibe un solo número como entrada.
En otras palabras, tenemos que forzar que la tubería se evalúe antes que la coma.
$ jq -n '[ 9|sqrt, (1|exp) ]'
[
3,
2.718281828459045
]
Del manual
jq admite el mismo conjunto de tipos de datos que JSON: números, strings, booleanos, arrays, objetos (que en jerga JSON son hashes cuyas claves son solo strings) y «null».
Aprenderás más sobre ellos en los ejercicios siguientes.
Los espacios en blanco no son significativos en jq.
Usa espacios, tabulaciones y saltos de línea como quieras para dar formato a tu código.
No tenemos constancia de que exista ninguna guía de estilo de jq.
Los valores en jq son inmutables.
Los filtros que modifican un valor generan un valor nuevo.
Esto implica que jq no tiene variables globales: tendrás que acostumbrarte a pasar el estado de un filtro a otro.
Los valores false y null se consideran falsos. Cualquier otro valor
(incluidos el número cero y el string, array u objeto vacío) es verdadero.
Sin entrar en demasiada profundidad (las funciones serán el tema de otro ejercicio), aquí tienes algunas funciones integradas útiles:
Dado un array como entrada, genera como salida el número de elementos del array.
$ jq -n '[10, 20, 30, 40] | length'
4
Este operador hace cosas distintas según el tipo de sus operandos: suma números, concatena strings, añade arrays, combina objetos.
$ jq -c -n '
3 + 4,
"foo" + "bar",
["a", "b"] + ["c"],
{"m": 10} + {"n": 20}
'
7
"foobar"
["a","b","c"]
{"m":10,"n":20}
add es una función que toma un array y devuelve un elemento con todos sus elementos sumados entre sí según las reglas de +.
[1, 2, 3] | add genera 6.
Dado un array como entrada y un filtro como argumento, genera un array en el que el filtro se aplica a cada elemento
$ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
[2,4,6,8]
Dada alguna entrada y un filtro como argumento:
null, de verdad ninguna salida)Por ejemplo, dados unos números, selecciona los que sean divisibles por 3
$ jq -n 'range(10) | select(. % 3 == 0)'
0
3
6
9
Recuerda que range genera un flujo de números.
Se invocará select una vez por cada número.
Solo se genera la salida de los números que «pasan» la expresión.
A menudo necesitas seleccionar elementos de un array. Hay un par de formas de hacerlo.
Con la entrada ["Anne", "Bob", "Cathy", "Dave"], selecciona los nombres que tengan longitud 4.
usa map y select juntos
map(select(length == 4))
descompón el array en elementos, aplica select a ese flujo y recoge los resultados
[ .[] | select(length == 4) ]
Los comentarios empiezan con el carácter # y continúan hasta el final de la línea.