Tracks
/
jq
jq
/
Temario
/
Fundamentos
Fu

Fundamentos en jq

1 ejercicio

Acerca de Fundamentos

jq funciona pasando los datos JSON entrantes a través de una expresión única (escrita como una tubería de filtros) para lograr los datos transformados deseados.

Una introducción rápida a las opciones de línea de comandos de jq

El lenguaje jq es implementado por el programa jq. Este programa proporciona varias opciones de línea de comandos útiles para controlar cómo se consume la entrada y cómo se presenta la salida.

En los ejemplos a continuación encontrarás:

  • -n o --null-input

    Normalmente, al programa jq se le da un archivo para leer, o le envías datos a su entrada. La opción --null-input te permite generar datos JSON sin ninguna entrada.

  • -c o --compact-output

    jq formatea su salida de manera legible por defecto. Es extremadamente útil para los humanos ver los datos cuando están bien formateados. Sin embargo, eso no es necesario para las máquinas: la opción --compact-output elimina los espacios en blanco de formateo 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. sed y awk usan la opción -f para el mismo propósito. Verás esto usado en los scripts de prueba para los ejercicios de práctica.

Consulta el manual para detalles sobre todas las opciones.

Filtros y tuberías

Los filtros también se conocen como expresiones.

Un filtro toma una entrada y produce una salida. Al igual que trabajas en una shell de Unix, puedes unir filtros con una tubería | para conectar la salida de un filtro a la entrada de otro.

El filtro de identidad: .

Este es el filtro más simple. Simplemente pasa su entrada a su salida. Por ejemplo, jq formatea de manera legible por defecto, así que pasar JSON al filtro . ¡da una salida bien formateada gratis!

$ echo '[1, 2, 3]' | jq '.'
[
  1,
  2,
  3
]

Arrays

Esta será una introducción rápida al trabajo con arrays. Cubriremos este tema con mayor detalle más adelante.

Los elementos del array se acceden con corchetes, y están indexados desde cero.

$ echo '[10, 20, 30]' | jq '.[1]'
20

Un filtro puede construir un array envolviendo una expresión en [ y ]

  • con una lista conocida de elementos:

    jq -n '[1, 2, 3]'
    
  • para recolectar un flujo de elementos: por ejemplo, range es una función que produce un flujo de números

    $ jq -n 'range(10; 70; 15)'
    10
    25
    40
    55
    

    Usar [] recolecta los resultados de la expresión en un array

    $ jq -c -n '[range(10; 70; 15)]'
    [10,25,40,55]
    

La coma es un operador

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 recolectar el resultado de unir las tres expresiones 1, 2 y 3.

¿Notaste los puntos y coma en range(10; 70; 15) de arriba? Como las comas tienen un propósito específico en el lenguaje jq, las funciones que toman múltiples argumentos usan puntos y coma para separar los argumentos.

Objetos

Una introducción rápida a los objetos.

Similar a muchos lenguajes de programación, usa puntos para acceder a las propiedades de los objetos

$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"

También se pueden usar corchetes para los objetos, pero entonces se necesitan comillas para los literales de string. Este es un método 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 se requieren comillas alrededor de las claves que son strings "simples".

jq -n '{question: (6 * 9), answer: 42}'

produce

{
  "question": 54,
  "answer": 42
}

Para tratar la clave como una expresión, debes encerrarla entre paréntesis (lo siguiente también produce lo mismo que arriba).

echo '[["question", "answer"], [54, 42]]' \
| jq '{(.[0][0]): .[1][0], (.[0][1]): .[1][1]}'
Note

Es bastante común 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 esto es tan común que existe una sintaxis abreviada para ello:

{id, name}

Tuberías

Por ejemplo, dado file.json que contiene

{
  "key1": "value1",
  "key2": [5, 15, 25]
}

Calculemos la longitud del array key2:

$ jq '.key2 | length' file.json
3

Estamos pasando por tubería la salida de la expresión .key2 como la entrada a length, que como era de esperar produce el número de elementos del array.

Caution

Este es un aspecto de jq que requiere acostumbrarse: la mayoría (pero no todas) de las funciones actúan como filtros donde pasas datos a la entrada del filtro, no como un argumento.

Los filtros pueden ignorar su entrada

En este ejemplo, los datos JSON de entrada se ignoran y no tienen impacto en la salida:

$ echo '{"answer": 42}' | jq '6 * 9'
54

Los filtros pueden producir flujos de datos

Un filtro puede producir más de un valor. Por ejemplo, el filtro .[] produce cada elemento de un array como un valor separado:

$ jq -n -c '[1, 2, 3]'
[1,2,3]

$ jq -n -c '[1, 2, 3] | .[]'
1
2
3

Al pasar por tubería un filtro así a 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. Una vez que entiendas esta técnica, te darás cuenta de que los filtros de jq muy potentes pueden ser muy concisos.

Paréntesis

Los paréntesis se usan para agrupar subexpresiones y forzar el orden de las operaciones, igual que en otros lenguajes. En jq, la necesidad de usarlos puede parecer algo 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.

Necesitamos asegurarnos de que exp solo tome un número como entrada. En otras palabras, debemos forzar que la tubería se evalúe antes de la coma.

$ jq -n '[ 9|sqrt, (1|exp) ]'
[
  3,
  2.718281828459045
]

Tipos

Del manual

jq admite el mismo conjunto de tipos de datos que JSON: números, strings, booleanos, arrays, objetos (que en jerga de JSON son hashes con solo claves de tipo string) y «null».

Aprenderás más sobre estos en ejercicios posteriores.

Espacios en blanco

Los espacios en blanco no son significativos en jq. Usa espacios, tabulaciones o saltos de línea como mejor te parezca para formatear tu código. No conocemos ninguna guía de estilo de jq existente.

Valores inmutables

Los valores en jq son inmutables. Los filtros que modifican un valor producirán un nuevo valor. Esto implica que jq no tiene variables globales; tendrás que acostumbrarte a pasar el estado de un filtro a otro.

«Veracidad»

Los valores false y null se consideran falsos. Cualquier otro valor (incluyendo el número cero y el string/array/objeto vacío) es verdadero.

Funciones y operadores

Sin entrar en gran profundidad (las funciones serán un tema para otro ejercicio), aquí hay algunas funciones integradas útiles:

  • length

    Dado un array como entrada, produce 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, fusiona 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 los elementos sumados usando las reglas de +. [1, 2, 3] | add produce 6.

  • map

    Dado un array como entrada y un filtro como argumento, produce un array donde el filtro se aplica a cada elemento

    $ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
    [2,4,6,8]
    
  • select

    Dado algo de entrada y un filtro como argumento:

    • si el filtro aplicado al argumento da como resultado un valor verdadero, produce la entrada sin cambios
    • de lo contrario, no produce nada (no el valor null, realmente ninguna salida)

    Por ejemplo, dados algunos números, selecciona los divisibles entre 3

    $ jq -n 'range(10) | select(. % 3 == 0)'
    0
    3
    6
    9
    

    Recuerda que range produce un flujo de números. select se invocará una vez por cada número. Solo se emiten 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.

    • usar map y select juntos

      map(select(length == 4))
      
    • expandir el array en elementos, hacer select en ese flujo y recolectar los resultados

      [ .[] | select(length == 4) ]
      

Comentarios

Los comentarios comienzan con un carácter # y continúan hasta el final de la línea.

Editar en GitHub El enlace se abre en una ventana o pestaña nueva

Aprende Fundamentos