Zsh: Configuración, personalización y algo más
Resolver conflictos en Git. Merge, Squash, Rebase o Pull

Guía del comando jq

Comando jq

En este artículo vamos a manipular textos JSON desde la terminal con el comando jq. Conocer este comando nos permitirá tener un resultado a nuestro gusto de este tipo de formato, algo que nos ayudará a navegador por documentos y filtrar información, es muy útil para trabajar con APIs o realizar scripting.

Índice:

¿Qué es JSON?

Antes de explicar que hace el comando JQ hago un pequeño inciso en explicar que es JSON. «JavaScript Object Notation» es un formato de representación de datos que se utiliza para almacenar y transferir datos entre diferentes capas de una aplicación; almacena los datos en pares «key: value».

La sintaxis de JSON deriva de JavaScript, pero es independiente del lenguaje. Es compatible con muchos lenguajes de programación; estos lenguajes incluyen código que puede ser usado para integrar JSON en el programa; pero desafortunadamente, no podemos trabajar con JSON directamente en el shell de Linux ya que no puede interpretarlo. Aquí es donde aparece el comando JQ, que nos ayudará a manipular JSONs en el shell de Linux.

Por cierto, antes de meternos en el lío con jq, en el mundo de la informática hay dudas de como se pronuncia JSON, cada uno lo lee como quiere. El precedente original es de Douglas Crockford, su desarrollador, él marca como tónica la segunda sílaba: /yeisón/. Otro día hablaremos de como se pronuncia Kubernetes 😂

Douglas Crockford
Douglas Crockford

¿Para qué sirve el comando JQ?

Algunas cosas que debes saber:

  • jq es como sed para datos JSON: se puede usar para segmentar, filtrar, mapear y transformar datos estructurados con la misma facilidad con la que sed, awk, grep y similares permiten jugar con el texto.
  • Está escrito en C y no tiene dependencias de tiempo de ejecución. Puedes descargar un solo binario, enviarlo a una máquina lejana del mismo tipo y esperar que funcione.
  • jq puede convertir el formato de datos que tiene en el que desea con muy poco esfuerzo, y el programa para hacerlo suele ser más corto y simple de lo que esperaría.

¿Cómo instalar el comando JQ?

El comando JQ no está disponible en la mayoría de distribuciones Linux de forma predeterminada, así que se seguramente debes descargarlo en el sistema. Puede hacerlo como cualquier otro paquete, por ejemplo, en distros derivadas de Debian:

sudo apt update
sudo apt install jq

En la página oficial puedes encontrar otras formas de descarga e instalación.

Podemos verificar la instalación poniendo en la shell directamente el comando y nos aparecerá la versión, la sintaxis y alguna información más extraída de la ayuda.

jq
jq - commandline JSON processor [version 1.6]
...
...

Sintaxis y opciones

El comando jq tiene un montón de posibilidades como veréis en su manual. Pero vamos a centrarnos en lo básico.

Su sintaxis es esta:

jq [options]  [file...]
jq [options] --args  [strings...]
jq [options] --jsonargs  [JSON_TEXTS...]

Y algunas de las opciones:

  -c               Hacer compacta la salida.
  -n               Usar `null` como único valor de entrada.
  -e               Establece el status code en función de la salida.
  -s               Lee (sorbe) todos los inputs de un array; aplicarle un filtro.
  -r               Genera strings sin procesar, no textos JSON.
  -R               Lee cadenas sin procesar, no textos JSON.
  -C               Colorear JSON.
  -M               Monocromo (no colorear JSON).
  -S               Ordenar keys de los objetos en la salida.
  --tab            Tabulaciones en la indentación.
  --arg a v        Establece la variable $a en el value <v>.
  --argjson a v    Establece la variable $a en el value JSON <v>.
  --slurpfile a f  Establece la variable $a en un array de textos JSON leídos desde <f>.
  --rawfile a f    Establece la variable $a en un string que consta del contenido de <f>.
  --args           Los argumentos restantes son argumentos de string, no archivos.
  --jsonargs       Los argumentos restantes son argumentos JSON, no archivos.
  --               Terminar el procesamiento de argumentos.

Organizar datos JSON con el comando JQ

La forma más simple y frecuente del uso de jq es para organizar los datos JSON al imprimirlos para que sean mejor legibles. Por ejemplo, en el fichero empleados.json tenemos este texto:

{"trabajadores":[{"Nombre": "Armando Guerra","id": "003"},{"Nombre": "Clara mente","id": "004"}]}

Si utilizamos cat lo veremos tal cuál lo tenemos arriba, pero usando el comando jq con el filtro ‘.’:

jq . empleados.json

Tendremos la siguiente salida:

{
  "trabajadores": [
    {
      "Nombre": "Armando Guerra",
      "id": "003"
    },
    {
      "Nombre": "Clara mente",
      "id": "004"
    }
  ]
}

¡Precioso! Ahora tenemos los datos organizados de manera que se entienden a simple vista. Además con colorines, ya veréis. Este filtro es especialmente necesario cuando se accede a datos de API. Los datos almacenados en las API pueden estar muy desorganizados y confusos.

Además, también lo podemos hacer en una tubería con el mismo resultado:

echo '{"trabajadores":[{"Nombre": "Armando Guerra","id": "003"},{"Nombre": "Clara mente","id": "004"}]}' | jq

Acceder a una propiedad concreta

Ya te lo imaginaras. El filtro .field se puede usar para acceder a las propiedades del objeto en el shell. Si solo queremos imprimir en pantalla una sola propiedad podemos usar el campo concreto que queremos. Por ejemplo, para acceder a los trabajadores en el mismo fichero de antes:

jq .trabajadores empleados.json

Tendremos la salida:

[
  {
    "Nombre": "Armando Guerra",
    "id": "003"
  },
  {
    "Nombre": "Clara mente",
    "id": "004"
  }
]

O para acceder a los elementos dentro de la propiedad, como los nombres de los trabajadores:

jq '.trabajadores[].Nombre' empleados.json

Con la salida:

"Armando Guerra"
"Clara mente"

Como es un Array podemos escoger el primer campo con:

 jq '.trabajadores[0].Nombre' empleados.json

Con la salida

"Armando Guerra"

Vamos a darle más brillo al asunto.

Arrays JSON

Vamos a explicar un poco más detallado los arrays en los datos JSON. Por lo general, usamos los arrays para representar una lista de elementos indicando cuando empieza y acaban con los corchetes ‘[ ]‘.

Iteración

Un ejemplo básico:

echo '["x","y","z"]' | jq '.[]'

Veremos que usando el operador iterador de valor de objeto .[], imprimirá cada elemento de la matriz en una línea separada:

"X"
"y"
"z"

Ahora imaginemos que queremos representar una lista de frutas en un documento JSON:

{
"frutas": [
  {
    "nombre": "manzana",
    "color": "verde",
    "precio": 1.2
  },
  {
    "nombre": "plátano",
    "color": "amarillo",
    "precio": 0.5
  },
  {
    "nombre": "kiwi",
    "color": " verde",
    "precio": 1
  }
]
}

Cada elemento de la matriz es un objeto que representa una fruta. Como el ejemplo anterior de los trabajadores podemos extraer el nombre de cada fruta de cada objeto del array:

jq '.[] | .nombre' frutas.json

Con la salida

"manzana"
"plátano"
"kiwi"

Acceso por índice

Por supuesto, como en todos los arrays y como hemos visto antes, podemos acceder a uno de los elementos de la matriz directamente pasando el índice:

jq '.[1].precio' frutas.json

Slicing

Finalmente, jq también admite el slicing de arrays, otra característica poderosa. Particularmente es útil cuando necesitamos devolver un subarreglo de un arreglo. En el ejemplo siempre se ve mejor, vamos a verlo con un array simple de números:

echo '[1,2,3,4,5,6,7,8,9,10]' | jq '.[6:9]'

Tendrá la salida de un nuevo array pero con los número del índice entre el 6 y el 9.

 
[
  7,
  8,
  9
]

Redirigiendo la tubería lo podríamos guardar en un archivo:

echo '[1,2,3,4,5,6,7,8,9,10]' | jq '.[6:9]' > guardaditoqueda.json

También es posible omitir uno de los índices al usar la funcionalidad de slicing. Probad esto:

echo '[1,2,3,4,5,6,7,8,9,10]' | jq '.[:6]' | jq '.[-3:]'

Especificamos .[:6] en el primer jq, con lo que el segmento comenzará desde el principio del array hasta el índice 6. Es lo mismo que hacer .[0:6].

Ahora, en la segunda operación de slicing tenemos un argumento negativo, lo que denota en este caso que la comienza en el final del array y hacia atrás, pero del resultado del primer jq. Con lo que tendremos la siguiente salida:

[
  4,
  5,
  6
]

Imagina las posibilidades…

Uso de funciones

jq tiene muchas funciones integradas que podemos usar para realizar una variedad de operaciones útiles. Echemos un vistazo a algunos.

Obtener keys

Si queremos las keys de un objeto como una matriz en lugar de los valores, podemos usar la función de keys:

jq '.frutas[1] | keys' frutas.json

Output:

[
  "color",
  "nombre",
  "precio"
]

Length

Otra función útil para los arrays y objetos es la función de length para saber la longitud o el número de propiedades del array. Siguiendo con el ejemplo de las frutas:

jq '.frutas | length' frutas.json

Nos dirá que tenemos 3 frutas en nuestro array.

Incluso podemos usar la función de longitud en valores string:

jq '.frutas[0].nombre | length' frutas.json

Nos dará como salida «7» porque son los caracteres que tiene la primera frutas: manzana.

Mapping

Otra función interesante es map para aplicar filtros o funciones en los arrays. Por ejemplo, con «has» nos dirá si tiene o no tiene la key que indiquemos cada uno de los elementos con true o false:

jq '.frutas | map(has("color"))' frutas.json

También podemos aplicar operaciones a los elementos, por ejemplo, vamos a subir el precio a todas las frutas:

jq '.frutas | map(.precio+2)' frutas.json

Output:

[
  3.2,
  2.5,
  3
]

Mínimo y máximo

Si necesitamos encontrar el elemento mínimo o máximo de un array, podemos utilizar las funciones min y max. Por ejemplo, el precio más caro:

jq '[.frutas[].precio] | max' frutas.json

Nos arrojará «1,2». Si te fijas, hemos construido otra matriz con los corchetes para que pueda procesar la función.

Seleccionar valores

Podemos pensar en la función «select» como una versión simple de XPath para JSON. Para seleccionar las frutas más caras de 0,5:

jq '.frutas[] | select(.precio>0.5)' frutas.json

También podemos seleccionar una propiedad:

jq '.frutas[] | select(.color=="amarillo")' frutas.json

Incluso podemos combinar las condiciones anteriores para construir selecciones más complejas:

jq '.frutas[] | select(.color=="verde" and .precio>1.1)' frutas.json

Expresiones regulares

Con la función test podemos utilizar expresiones regulares. Por ejemplo:

jq '.frutas[] | select(.nombre|test("^m.")) | .precio' frutas.json

Encontrar valores únicos

Podemos necesitar buscar una ocurrencia única de un valor particular dentro de un array o eliminar duplicados. Podemos ver los colores únicos con el siguiente comandos:

jq '.frutas | map(.color) | unique' frutas.json

Transformación de JSON

Frecuentemente cuando se trabaja con estructuras de datos como JSON, podemos querer transformar una estructura de datos en otra. Esto puede ser útil al trabajar con grandes estructuras JSON cuando sólo estamos interesados en algunas propiedades o valores.

En este ejemplo, utilizaremos un JSON de Wikipedia que describe una lista de entradas de páginas:

{
  "query": {
    "pages": [
      {
        "21721040": {
          "pageid": 21721040,
          "ns": 0,
          "title": "Stack Overflow",
          "extract": "Some interesting text about Stack Overflow"
        }
      },
      {
        "21721041": {
          "pageid": 21721041,
          "ns": 0,
          "title": "Baeldung",
          "extract": "A great place to learn about Java"
        }
      }
    ]
  }
}

En realidad sólo nos interesa el título y el extracto de cada entrada de página.

Así pues, veamos cómo podemos transformar este documento:

jq '.query.pages | [.[] | map(.) | .[] | {page_title: .title, page_description: .extract}]' wikipedia.json

Vamos a ver el comando con más detalle para entenderlo bien:

  • Primero, empezamos accediendo al array de pages y pasando ese array al siguiente filtro del comando mediante una tubería.
  • Luego iteramos sobre este array y pasamos cada objeto dentro del array pages a la función map, donde simplemente creamos un nuevo array con el contenido de cada objeto.
  • A continuación, iteramos sobre esta matriz y para cada elemento creamos un objeto que contenga las dos claves page_title y page_description.
  • Las referencias .title y .extract se utilizan para rellenar las dos nuevas claves.

Esto nos da una nueva estructura JSON más sencilla:

[
  {
    "page_title": "Stack Overflow",
    "page_description": "Some interesting text about Stack Overflow"
  },
  {
    "page_title": "Baeldung",
    "page_description": "A great place to learn about Java"
  }
]

Conclusiones

Hemos visto algunas de las posibilidades básicas del comando jq para procesar y manipular JSON a través de la misma línea de comandos. Lo genial es que no hemos visto nada de todo lo que podemos utilizar.

Como extra, os dejo un par de herramientas:

Más apuntes

Invítame a un café con bitcoins:
1QESjZDPxWtZ9sj3v5tvgfFn3ks13AxWVZ

Bitcoins para café
También puedes invitarme a algo para mojar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Rellena este campo
Rellena este campo
Por favor, introduce una dirección de correo electrónico válida.
Tienes que aprobar los términos para continuar