Rutas
/
jq
jq
/
Ejercicios
/
Lista de la compra
Lista de la compra

Lista de la compra

Ejercicio de aprendizaje

Introducción

Conceptos básicos

Asumiremos que ya conoces la sintaxis de JSON y sus tipos de datos.

jq funciona pasando los datos JSON de entrada por una única expresión (escrita como una tubería de filtros) para obtener los datos transformados que deseas.

Note

El lenguaje jq está implementado por el programa jq. Este programa ofrece varias opciones de línea de comandos muy útiles 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

    Por defecto, jq presenta su salida de forma legible. Para las personas es muy útil 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 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 con el mismo propósito. Verás que se usa en los scripts de pruebas de los ejercicios de práctica.

El resto de esta lección se centrará en el lenguaje jq.

Filtros y tuberías

Los filtros también se conocen como expresiones.

Un filtro recibe 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.

El filtro de identidad: .

Este es el filtro más sencillo. Simplemente pasa su entrada a su salida.

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

Arrays

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

Se accede a los elementos del 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 recopilar 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 [] recopila 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 arrays [] para recopilar el resultado de unir las tres expresiones 1, 2 y 3.

¿Te has fijado en los puntos y coma de range(10; 70; 15), más arriba? Como las comas tienen un propósito específico en el lenguaje jq, las funciones que reciben varios argumentos usan puntos y coma para separar los argumentos.

Objetos

Una introducción rápida a los objetos.

Como en muchos lenguajes de programación, se usan puntos para acceder a las propiedades de un objeto

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

También se pueden usar corchetes con los objetos, pero entonces hacen falta 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 hacen falta 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 envolverla entre paréntesis (lo siguiente también produce lo mismo que el ejemplo anterior).

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

Tuberías

Por ejemplo, dado file.json con el siguiente contenido

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

Vamos a calcular la longitud del array key2:

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

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

Los filtros pueden ignorar su entrada

En este ejemplo, los datos JSON de entrada se ignoran y no influyen 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 independiente:

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

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

Si conectas un filtro así a otro mediante una tubería, 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 los filtros jq más 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, puede resultar algo sorprendente que hagan falta.

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 reciba un número como entrada. En otras palabras, debemos forzar que la tubería se evalúe antes que la coma.

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

Funciones y operadores

Sin entrar en demasiada profundidad (las funciones serán el tema de otro ejercicio), aquí tienes 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 recibe un array y devuelve un elemento con todos los elementos sumados entre sí según las reglas de +. [1, 2, 3] | add produce 6.

  • map

    Dado un array como entrada y un filtro como argumento, produce 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]
    
  • select

    Dada alguna entrada y un filtro como argumento:

    • si el filtro aplicado al argumento da como resultado un valor true, produce la entrada sin cambios
    • en caso contrario, no produce nada (no el valor null, sino realmente ninguna salida)

    Por ejemplo, dados algunos números, selecciona los que son divisibles por 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 producen los números que «pasan» la expresión.

    A menudo necesitas seleccionar elementos de un array. Hay un par de formas de hacerlo.

    Dada la entrada ["Anne", "Bob", "Cathy", "Dave"], selecciona los nombres que tienen longitud 4.

    • usar map y select juntos

      map(select(length == 4))
      
    • descomponer el array en elementos, aplicar select a ese flujo y recopilar los resultados

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

Comentarios

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

Instrucciones

De repente te ha entrado un antojo de tortitas. Como persona moderna con inclinaciones técnicas, escribes una lista de la compra con los ingredientes de las tortitas en formato JSON. La estructura de la lista es:

{
  "name": "name of shopping list",
  "ingredients": [ ...list of ingredients... ],
  "optional ingredients": [ ...list of ingredients... ]
}

Un ingrediente se representa como un objeto JSON, así:

{
  "item": "flour",
  "amount": {
    "quantity": 2,
    "unit": "cup"
  }
}

Un ingrediente tiene una propiedad opcional llamada "substitute" que contiene un string con otro ingrediente que se puede usar en su lugar.

Vamos a ver cómo podemos examinar la lista de la compra con jq.

1. Extrae el nombre de la lista de la compra

Escribe una expresión que devuelva el elemento "name" de la lista de la compra.

2. Cuenta cuántos ingredientes se necesitan

Cuenta solo los ingredientes «obligatorios». No incluyas los ingredientes opcionales.

3. ¿Cuánta azúcar?

Escribe una expresión que devuelva la cantidad de azúcar. Solo se quiere la parte numérica.

4. Mapea los ingredientes que se pueden sustituir

Algunos ingredientes se pueden sustituir (si no tienes el ingrediente X puedes usar Y). Genera un objeto JSON que asocie el ingrediente recomendado con su sustituto. Incluye los ingredientes opcionales en esa correspondencia.

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

¿Listo para empezar Lista de la compra?

Regístrate en Exercism para aprender y dominar jq con 12 conceptos75 ejercicios y mentoría humana real, todo gratis.