32blogby Studio Mitsu

Guía completa de jq: domina el procesamiento JSON en la terminal

Aprende jq desde filtros básicos hasta transformación avanzada de JSON: respuestas de API, archivos de configuración e integración con scripts.

by omitsu16 min read
Contenido

Necesitas extraer campos específicos de una respuesta de API. Parsear un package.json para listar dependencias. Filtrar entradas de error de logs en formato JSON. Todo desde la terminal, sin escribir un script.

Eso es lo que hace jq. Conocido como la navaja suiza del procesamiento JSON, maneja el formateo, filtrado, transformación y agregación de datos JSON — todo en un solo comando.

Esta guía recorre todo, desde filtros básicos hasta ejemplos de scripting del mundo real, con comandos listos para ejecutar.

¿Qué es jq?

jq es un procesador JSON ligero de línea de comandos escrito en C. Se distribuye como un binario único sin dependencias externas. El código fuente está disponible en GitHub bajo la licencia MIT.

Características clave:

  • Pretty-printing — formatea JSON minificado en una salida legible
  • Extracción de campos — obtén exactamente los valores que necesitas
  • Filtrado — selecciona datos que cumplan condiciones específicas
  • Transformación — reestructura datos y calcula agregados
  • Encadenamiento — combina múltiples operaciones en una sola expresión

Así como sed y awk son las herramientas de referencia para el procesamiento de texto, jq es el estándar para JSON. Combinado con curl, es una parte esencial de cualquier kit de herramientas CLI moderno.

Instalación

powershell
# winget (recomendado)
winget install jqlang.jq

# Si usas WSL, instala vía el gestor de paquetes de Linux
# sudo apt install jq

Verifica la instalación:

bash
jq --version
text
jq-1.8.1

Uso básico

Pretty-print de JSON

El filtro más simple es . — toma la entrada y la imprime formateada.

bash
echo '{"name":"test","value":42,"active":true}' | jq '.'
json
{
  "name": "test",
  "value": 42,
  "active": true
}

Para leer desde un archivo, pasa el nombre del archivo como argumento:

bash
jq '.' data.json

Extraer campos

Usa notación de punto para acceder a campos específicos:

bash
echo '{"name":"jq","version":"1.8.1"}' | jq '.name'
text
"jq"

Para salida de cadena sin comillas, usa -r (salida raw):

bash
echo '{"name":"jq","version":"1.8.1"}' | jq -r '.name'
text
jq

Campos anidados

Encadena puntos para acceder a valores profundamente anidados:

bash
echo '{"data":{"users":[{"name":"Alice","age":30}]}}' | jq '.data.users[0].name'
text
"Alice"

Operaciones con arrays

bash
# Primer elemento
echo '[10,20,30]' | jq '.[0]'

# Último elemento
echo '[10,20,30]' | jq '.[-1]'

# Longitud del array
echo '[10,20,30]' | jq 'length'

# Porción (índice 1 a 2)
echo '[10,20,30,40]' | jq '.[1:3]'

Formato de salida

bash
# Salida compacta (una sola línea)
echo '{"name":"test","value":42}' | jq -c '.'

# Indentación con tabulaciones
echo '{"name":"test","value":42}' | jq --tab '.'

# Claves ordenadas
echo '{"b":2,"a":1}' | jq -S '.'

Filtros y pipes

El verdadero poder de jq reside en combinar filtros. Usa el operador pipe | para encadenar operaciones.

Iterador de arrays

[] expande cada elemento de un array:

bash
echo '{"users":[{"name":"Alice"},{"name":"Bob"},{"name":"Charlie"}]}' \
  | jq '.users[].name'
text
"Alice"
"Bob"
"Charlie"

select — filtrado condicional

select() mantiene solo los elementos que cumplen una condición:

bash
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25},{"name":"Charlie","age":35}]}' \
  | jq '.users[] | select(.age > 28)'
json
{
  "name": "Alice",
  "age": 30
}
{
  "name": "Charlie",
  "age": 35
}

map — transformar arrays

map() aplica un filtro a cada elemento y devuelve un nuevo array:

bash
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' \
  | jq '.users | map(.name)'
json
[
  "Alice",
  "Bob"
]

Construcción de objetos

Construye nuevos objetos con solo los campos que necesitas:

bash
echo '{"users":[{"name":"Alice","age":30,"email":"alice@example.com","role":"admin"}]}' \
  | jq '.users[] | {name: .name, email: .email}'
json
{
  "name": "Alice",
  "email": "alice@example.com"
}

Interpolación de cadenas

Usa \() para incrustar valores en cadenas. Combina con -r para salida limpia:

bash
echo '{"users":[{"name":"Alice","email":"alice@example.com"},{"name":"Bob","email":"bob@example.com"}]}' \
  | jq -r '.users[] | "\(.name): \(.email)"'
text
Alice: alice@example.com
Bob: bob@example.com

Expresiones condicionales

bash
echo '{"status":"ok","data":"hello"}' \
  | jq 'if .status == "ok" then .data else "error: \(.status)" end'
text
"hello"

Ordenación, agrupación y agregación

bash
# Ordenar por campo
echo '[{"name":"Charlie","age":35},{"name":"Alice","age":30},{"name":"Bob","age":25}]' \
  | jq 'sort_by(.age)'

# Valores únicos
echo '["apple","banana","apple","cherry","banana"]' | jq 'unique'

# Suma
echo '[10,20,30,40]' | jq 'add'

# Promedio
echo '[10,20,30,40]' | jq 'add / length'

keys y has

bash
# Listar todas las claves
echo '{"name":"test","version":"1.0","license":"MIT"}' | jq 'keys'

# Verificar si existe una clave
echo '{"name":"test"}' | jq 'has("name")'

Casos de uso reales

Procesar respuestas de API

Combinar jq con curl para extraer datos de respuestas de API es uno de los casos de uso más comunes.

bash
curl -s https://api.github.com/repos/jqlang/jq \
  | jq '{name: .name, stars: .stargazers_count, forks: .forks_count, license: .license.spdx_id}'
json
{
  "name": "jq",
  "stars": 31000,
  "forks": 1600,
  "license": "MIT"
}

Combinar resultados de respuestas paginadas de API:

bash
for page in 1 2 3; do
  curl -s "https://api.github.com/users/octocat/repos?per_page=100&page=${page}"
done | jq -s 'flatten | map({name: .name, stars: .stargazers_count}) | sort_by(.stars) | reverse'

Parsear package.json

Inspecciona rápidamente las dependencias del proyecto sin abrir el archivo:

bash
# Listar nombres de dependencias
jq '.dependencies | keys' package.json
json
[
  "next",
  "react",
  "react-dom"
]
bash
# Mostrar paquetes con versiones
jq -r '.dependencies | to_entries[] | "\(.key)@\(.value)"' package.json
text
next@^16.0.0
react@^19.0.0
react-dom@^19.0.0
bash
# Contar dependencies vs devDependencies
jq '{deps: (.dependencies | length), devDeps: (.devDependencies | length)}' package.json

Análisis de logs JSON

Procesar archivos de log en formato JSON Lines (un objeto JSON por línea):

bash
# Extraer logs de error con marcas de tiempo
cat app.log \
  | jq -r 'select(.level == "error") | "\(.timestamp) [\(.level)] \(.message)"'
text
2026-03-08T10:15:30Z [error] Database connection timeout
2026-03-08T10:18:45Z [error] Failed to process request
bash
# Contar entradas por nivel de log
cat app.log \
  | jq -s 'group_by(.level) | map({level: .[0].level, count: length})'
json
[
  { "level": "error", "count": 5 },
  { "level": "info", "count": 142 },
  { "level": "warn", "count": 23 }
]

Editar archivos de configuración

Modifica archivos de configuración JSON desde la línea de comandos:

bash
# Actualizar un valor
jq '.database.port = 5433' config.json > tmp.json && mv tmp.json config.json
bash
# Añadir un campo
jq '.database.ssl = true' config.json > tmp.json && mv tmp.json config.json
bash
# Eliminar un campo
jq 'del(.debug)' config.json > tmp.json && mv tmp.json config.json
bash
# Fusionar dos archivos JSON (el override tiene precedencia)
jq -s '.[0] * .[1]' base.json override.json > merged.json

Técnicas avanzadas

Pasar variables de shell de forma segura (--arg / --argjson)

Frecuentemente necesitarás usar variables de shell dentro de filtros jq. La interpolación directa de cadenas es frágil — usa --arg (cadenas) y --argjson (números, arrays, objetos) en su lugar.

bash
# --arg: pasar como cadena
username="Alice"
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' \
  | jq --arg name "${username}" '.users[] | select(.name == $name)'
json
{
  "name": "Alice",
  "age": 30
}
bash
# --argjson: pasar como número o valor JSON (sin comillas)
min_age=28
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' \
  | jq --argjson min "${min_age}" '.users[] | select(.age >= $min)'
bash
# Construir dinámicamente un objeto JSON
key="version"
value="2.0"
jq -n --arg k "${key}" --arg v "${value}" '{($k): $v}'
json
{
  "version": "2.0"
}

--arg siempre pasa el valor como cadena. Si necesitas comparación numérica, usa --argjson. Pasar "28" vía --arg no coincidirá con .age (número 28) en una comparación como select(.age >= $min) porque los tipos difieren.

Manejo de errores (try-catch / operador ?)

Esencial al procesar JSON incompleto o campos que pueden no existir.

bash
# Operador ?: suprimir errores y continuar (atajo para try)
echo 'null' | jq '.foo?'

.foo? no genera un error de acceso a campo sobre null — devuelve null silenciosamente. Útil cuando las estructuras de datos varían en un pipeline.

bash
# Operador //: devolver un respaldo cuando el valor es null o false
echo '{"a":1} {"b":2} {"a":3}' | jq '.a // empty'
text
1
3
bash
# try-catch: devolver un valor de respaldo en caso de error
echo '{"data":"not-a-number"}' \
  | jq 'try (.data | tonumber) catch "parse error"'
text
"parse error"
bash
# Manejar arrays donde algunos elementos carecen de campos
echo '[{"name":"Alice","email":"a@example.com"},{"name":"Bob"}]' \
  | jq '.[] | {name, email: (.email // "N/A")}'
json
{
  "name": "Alice",
  "email": "a@example.com"
}
{
  "name": "Bob",
  "email": "N/A"
}

Cadenas de formato (@base64 / @uri / @html / @tsv)

jq tiene formateadores incorporados para convertir la salida a formatos específicos.

bash
# @base64: codificar en Base64
echo '{"token":"hello:world"}' | jq -r '.token | @base64'
text
aGVsbG86d29ybGQ=
bash
# @base64d: decodificar Base64
echo '"aGVsbG86d29ybGQ="' | jq -r '@base64d'
text
hello:world
bash
# @uri: codificar URL (útil para construir parámetros de consulta)
echo '{"q":"jq filter examples","lang":"ja"}' \
  | jq -r '"https://example.com/search?q=\(.q | @uri)&lang=\(.lang)"'
text
https://example.com/search?q=jq%20filter%20examples&lang=ja
bash
# @tsv: convertir arrays a TSV (valores separados por tabulaciones)
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' \
  | jq -r '.[] | [.name, .age] | @tsv'
text
Alice	30
Bob	25

Procesamiento streaming de JSON grandes (--stream)

Procesar un archivo JSON de cientos de megabytes con jq '.' carga todo en memoria. --stream lo procesa incrementalmente como pares de ruta-valor.

bash
# --stream: procesamiento incremental (ahorra memoria)
echo '{"users":[{"name":"Alice"},{"name":"Bob"}]}' \
  | jq --stream 'select(.[0][-1] == "name") | .[1]'
text
"Alice"
"Bob"
bash
# Extraer una clave específica de un archivo enorme sin cargarlo completo
jq --stream 'select(.[0][0] == "error_count") | .[1]' huge_report.json

El modo streaming usa una sintaxis de filtros diferente al modo normal, así que tiene una curva de aprendizaje. Pero cuando el modo regular se queda sin memoria, esta es tu alternativa.

Acceder a variables de entorno ($ENV / env)

Útil en pipelines CI/CD y scripts donde necesitas incorporar variables de entorno en JSON.

bash
export APP_VERSION="1.5.0"
export APP_ENV="production"

# $ENV: acceder a variables de entorno específicas
jq -n '{version: $ENV.APP_VERSION, env: $ENV.APP_ENV}'
json
{
  "version": "1.5.0",
  "env": "production"
}
bash
# env: acceder a todas las variables de entorno como objeto
jq -n 'env | keys | map(select(startswith("APP_")))'
json
[
  "APP_ENV",
  "APP_VERSION"
]

$ENV vs env: $ENV.KEY accede directamente a una clave específica. env devuelve todas las variables de entorno como objeto, permitiendo filtrar con keys o select.

Ejemplos de scripting

Integrar jq en scripts de shell facilita el procesamiento automatizado de JSON. Combínalo con xargs para canalizar valores extraídos a otros comandos en paralelo.

Exportar repos de GitHub a CSV

bash
#!/bin/bash
# github-repos-to-csv.sh
# Exportar los repositorios públicos de un usuario a formato CSV

USERNAME="${1:?Uso: $0 <github-username>}"
OUTPUT="repos.csv"

echo "name,stars,forks,language,updated" > "${OUTPUT}"

page=1
while true; do
  response=$(curl -s "https://api.github.com/users/${USERNAME}/repos?per_page=100&page=${page}")

  count=$(echo "${response}" | jq 'length')
  if [ "${count}" -eq 0 ]; then
    break
  fi

  echo "${response}" \
    | jq -r '.[] | [.name, .stargazers_count, .forks_count, (.language // "N/A"), .updated_at[:10]] | @csv' \
    >> "${OUTPUT}"

  page=$((page + 1))
done

total=$(tail -n +2 "${OUTPUT}" | wc -l)
echo "Exportados ${total} repositorios a ${OUTPUT}"

Puntos clave:

  • El filtro @csv maneja el formateo CSV automáticamente, incluyendo comillas y escape
  • // "N/A" es el operador alternativo — devuelve un valor por defecto cuando un valor es null o false
  • La paginación se gestiona con un bucle simple hasta obtener una respuesta vacía

Fusionar y agregar informes JSON

bash
#!/bin/bash
# merge-json-reports.sh
# Combinar archivos de informes JSON y generar estadísticas resumen

REPORT_DIR="${1:?Uso: $0 <directorio-de-informes>}"

if [ ! -d "${REPORT_DIR}" ]; then
  echo "Error: Directorio '${REPORT_DIR}' no encontrado" >&2
  exit 1
fi

file_count=$(find "${REPORT_DIR}" -name "*.json" -type f | wc -l)
if [ "${file_count}" -eq 0 ]; then
  echo "Error: No se encontraron archivos JSON en '${REPORT_DIR}'" >&2
  exit 1
fi

# Fusionar todos los archivos JSON y calcular resumen
find "${REPORT_DIR}" -name "*.json" -type f -exec cat {} + \
  | jq -s '{
    total_files: length,
    total_records: (map(.records // 0) | add),
    total_errors: (map(.errors // 0) | add),
    avg_duration_ms: (map(.duration_ms // 0) | add / length | floor),
    statuses: (group_by(.status) | map({status: .[0].status, count: length})),
    date_range: {
      earliest: (map(.timestamp) | sort | first),
      latest: (map(.timestamp) | sort | last)
    }
  }'

echo ""
echo "Procesados ${file_count} archivos de ${REPORT_DIR}"

Puntos clave:

  • -s (slurp) combina múltiples objetos JSON en un solo array
  • group_by y map juntos producen conteos por estado
  • // 0 proporciona valores de respaldo para campos ausentes
  • floor trunca los decimales en el promedio

Nota de seguridad

Al procesar JSON no confiable, ten en cuenta estos puntos:

  • Mantén jq actualizado — las vulnerabilidades del parser se descubren periódicamente. Si jq --version muestra algo por debajo de 1.8.1, actualiza inmediatamente
  • Limita el tamaño de entrada — procesar archivos JSON muy grandes puede consumir mucha memoria. Usa head -c antes en el pipeline para limitar el tamaño de entrada, o considera el parser streaming (--stream) para conjuntos de datos grandes
  • Protégete contra inyección de shell — nunca canalices la salida de jq directamente a eval o bash -c. Siempre captura los valores en variables entre comillas
bash
# Peligroso (nunca hagas esto)
eval $(curl -s https://example.com/config.json | jq -r '.command')

# Seguro (capturar en una variable)
value=$(curl -s https://example.com/config.json | jq -r '.setting')
echo "Configuración: ${value}"

Artículos relacionados

FAQ

¿Cuál es la diferencia entre jq y yq?

jq es un procesador exclusivo de JSON. yq también maneja YAML, XML y TOML, usando una sintaxis similar a jq internamente. Para trabajar solo con JSON, jq es más rápido y ligero. Si necesitas procesar YAML u otros formatos, vale la pena considerar yq.

¿Puedo usar jq sin instalarlo?

Sí — jq play es un playground online donde puedes probar filtros en tu navegador. Es genial para aprender antes de instalar o depurar expresiones complejas. Solo no pegues datos sensibles en él.

jq se queda sin memoria con archivos JSON grandes. ¿Qué puedo hacer?

Usa la opción --stream para procesar JSON incrementalmente como pares de ruta-valor, lo que reduce significativamente el uso de memoria. La sintaxis de filtros difiere del modo normal, pero es la solución ideal para archivos de cientos de megabytes o más.

¿Cómo convierto la salida de jq a CSV?

Usa el formateador @csv. Construye un array y pásalo a @csv: jq -r '.[] | [.name, .age] | @csv'. Maneja las comillas y el escape automáticamente. Para salida TSV, usa @tsv en su lugar.

¿Cómo uso variables de shell dentro de filtros jq?

Usa --arg (para cadenas) o --argjson (para números, arrays, objetos). La interpolación directa de cadenas es frágil y propensa a errores. Para comparaciones numéricas, debes usar --argjson — de lo contrario, la incompatibilidad de tipos entre una cadena y un número causará resultados inesperados.

¿Cómo manejo null o campos faltantes de forma segura en jq?

El operador // (operador alternativo) proporciona un valor de respaldo: .email // "N/A". El operador ? suprime errores: .foo?. Para más control, usa try-catch: try (.data | tonumber) catch "parse error".

¿Debería usar jq o el módulo json de Python?

Para one-liners y procesamiento en scripts de shell, jq es significativamente más rápido. Para lógica compleja (muchas ramificaciones, integración con bases de datos, etc.), Python ofrece mejor legibilidad. En pipelines CI/CD, se prefiere jq por sus dependencias mínimas.

Conclusión

jq es la herramienta esencial para el procesamiento de JSON en la línea de comandos.

  • Básico. para pretty-printing, .campo para extracción, -r para salida de cadenas sin comillas
  • Filtrosselect para filtrado condicional, map para transformación, pipes para encadenar
  • Mundo real — procesamiento de respuestas de API, edición de archivos de configuración, análisis de logs
  • Scripting — conversión con @csv, -s para fusionar, integración con scripts de shell

Combina curl para la obtención de datos, jq para el procesamiento JSON y sed & awk para el formateo de texto — y tendrás un potente pipeline de procesamiento de datos directamente en tu terminal. Empieza con curl ... | jq '.' y construye desde ahí.

Artículos relacionados: