JSON Toolkit

Validación de JSON en CI: un pipeline con jsonlint, ajv y jq

En resumen: La validación de sintaxis demuestra que un archivo se puede parsear; la validación de schema demuestra que los campos siguen el contrato. En CI, lo que importa son códigos de salida inequívocos, localizaciones de error y rutas de reparación.

Intermedio~10 min de lecturaPublicado el 2026-10-31Actualizado el 2026-10-31
Portada de validación de JSON en CI

En resumen: en CI, deja que jq empty se encargue de la sintaxis, ajv-cli de la estructura (check-jsonschema para equipos multiformato) y Prettier del estilo; encadénalos por capas y detente en el primer fallo.

Pasar el parser de tu editor local no significa que el archivo pertenezca a CI; pasar CI tampoco significa que los campos coincidan con el contrato.

1. Separa la validación de sintaxis de la validación de schema

La expresión "validación de JSON" se usa de forma demasiado amplia, y por eso los equipos a menudo eligen una sola herramienta para resolver dos problemas distintos:

Capa Pregunta que responde Herramientas típicas Qué significa un fallo
Sintaxis ¿Este archivo es JSON válido? jq empty, jsonlint, python -m json.tool Los corchetes, comas, comillas o la codificación están rotos
Schema ¿Los campos, tipos y propiedades obligatorias coinciden con el contrato? ajv-cli, check-jsonschema El archivo se parsea, pero la estructura de negocio es incorrecta
Estilo ¿La indentación y el orden de las claves son consistentes? Prettier, jq -S El formato es inconsistente y los diffs son ruidosos

Estas capas no pueden sustituirse unas a otras. Un archivo que pasa la sintaxis puede fallar el schema: {"price": "12.5"} es JSON válido, pero si el schema exige que price sea un número, no es aceptable. A la inversa, por completo que sea el schema, un archivo al que le falta una llave de cierre nunca compilará contra él.

Así que el pipeline debe ejecutar sintaxis primero, estructura después y estilo al final. Detente en cuanto falle una capa, de lo contrario un error de parseo se disfraza de error de campo.

2. Capa de sintaxis: jq empty es la opción con menos dependencias

Si jq ya está en la imagen de CI, la validación de sintaxis no cuesta casi nada:

jq empty data.json

jq empty no produce salida; solo parsea el archivo. Sale con 0 en caso de éxito y con un código distinto de cero más información de línea/columna en caso de fallo. Eso lo hace más ligero que traer un árbol de dependencias completo de Node, y una buena primera barrera.

Al validar por lotes archivos JSON de un repositorio, excluye node_modules, la salida de build y las cachés de terceros:

find . -name '*.json' \
  -not -path './node_modules/*' \
  -not -path './dist/*' \
  -print0 | xargs -0 -n1 jq empty

-print0 y xargs -0 manejan espacios en los nombres de archivo, mientras que -n1 garantiza un archivo por invocación para que el mensaje de error identifique al culpable. No pases cientos de archivos a un solo proceso; los mensajes de error se pisarán unos a otros.

jsonlint es otra opción habitual, normalmente invocada como npx jsonlint -q data.json, donde -q imprime solo los errores. Reporta números de línea y columna, pero su mantenimiento es menos activo que jq, y distintas versiones toleran de forma diferente BOM y comentarios. Trátalo como un comprobador de sintaxis más legible, no como un validador de schema.

3. Capa de schema: lo básico de ajv-cli validate

Ajv es el validador de schema más usado en el ecosistema JavaScript, y ajv-cli lo expone como herramienta de línea de comandos:

ajv validate -s schema.json -d data.json

-s apunta al archivo de schema y -d a los datos que se validan. En caso de fallo el proceso sale con un código distinto de cero, así que CI falla de inmediato. Los proyectos reales suelen añadir dos flags:

npx ajv-cli validate \
  -s schemas/order.json \
  -d 'fixtures/orders/*.json' \
  --strict=false \
  --errors=text

--strict=false ayuda con schemas antiguos: el modo estricto trata las palabras clave desconocidas y las declaraciones required sospechosas como errores, y muchos schemas heredados fallan por eso. --errors=text produce una salida legible por humanos, mientras que la estructura JSON predeterminada es mejor para máquinas. Elige según quién consuma el resultado.

La versión de draft del schema también debe coincidir. Las versiones mayores de Ajv soportan distintos drafts de forma predeterminada, y ajv-cli puede seleccionar uno con --spec=draft7, --spec=draft2019 o --spec=draft2020. Si un schema declara $schema: "https://json-schema.org/draft/2020-12/schema" pero la CLI lo compila como draft-07, algunas palabras clave se ignoran en silencio. Para más información sobre la creación de schemas y las diferencias entre versiones, consulta Primeros pasos con JSON Schema.

La validación de formatos es otro requisito frecuente. La palabra clave format es solo descriptiva de forma predeterminada; para que email, date-time y uri realmente impongan algo, carga ajv-formats:

npx ajv-cli validate -c ajv-formats -s schema.json -d data.json

Este paso es fácil de olvidar, y el resultado es que un campo de email acepta not-an-email. Antes de publicar, alimenta el pipeline con un registro deliberadamente inválido; eso demuestra que la regla está activa mucho mejor que leer la documentación diez veces.

check-jsonschema: múltiples formatos y errores más amigables

Si el equipo maneja YAML, TOML o configuración de GitHub Actions además de JSON, check-jsonschema es más cómodo:

check-jsonschema --schemafile schemas/config.json config/app.json
check-jsonschema --schemafile schemas/pipeline.json .github/workflows/ci.yml

Sus ventajas prácticas:

  1. Soporte nativo de YAML, que encaja con archivos que "tienen forma de JSON pero están escritos en YAML", como los manifiestos de Kubernetes y la configuración de CI.
  2. Los errores incluyen la ruta del archivo, el número de línea y la ruta del campo, manteniendo bajo el coste de localización.
  3. Proporciona un hook de pre-commit, así que la integración es barata.
  4. Puede obtener schemas comunes de SchemaStore, pero también acepta archivos locales, evitando una dependencia de red.

En equipos multilingües, Python suele estar ya presente, así que instalar y actualizar check-jsonschema es más ligero que mantener una cadena de herramientas Node. Deja que jq se encargue de la sintaxis y esta herramienta de la estructura; la combinación tiene un coste de mantenimiento muy bajo.

4. Los códigos de salida son la única señal fiable de CI

Por muy cuidada que sea la salida, CI solo lee códigos de salida. Tres trampas son comunes:

Síntoma Causa Corrección
Hay errores pero CI está en verde El comando termina con `
No hay errores pero CI está en rojo diff trata "diferencias encontradas" como fallo Distingue "diferencia" de "error de ejecución"
La salida de error está truncada La salida paralela de varios archivos se pisa a sí misma Ejecuta en serie o imprime por archivo

Recuerda la semántica de códigos de salida de jq: el parseo normal devuelve 0; con -e, un resultado de expresión false o null devuelve 1; los errores de uso y archivos inexistentes devuelven 2; los errores de compilación de script devuelven 3. Las herramientas de schema generalmente usan 0 para éxito, 1 para fallo de validación y 2 para errores de herramienta o argumentos. Al escribir un wrapper, no colapses cualquier código distinto de cero en "los datos están mal", o un fallo de entorno se diagnosticará erróneamente como un problema de contenido.

5. pre-commit: detecta problemas antes del commit

El valor de pre-commit no es "una comprobación más"; es dar feedback a los desarrolladores en un minuto en lugar de después de una ejecución de CI de diez minutos. Una configuración mínima se ve así:

repos:
  - repo: https://github.com/python-jsonschema/check-jsonschema
    rev: 0.33.0
    hooks:
      - id: check-jsonschema
        files: ^configs/.*\.json$
        args:
          - --schemafile
          - schemas/config.json
  - repo: local
    hooks:
      - id: jq-syntax
        name: jq syntax check
        entry: jq empty
        language: system
        files: ^configs/.*\.json$

Dos detalles importan:

  • files debe coincidir solo con los directorios que necesitan validación. No ejecutes hooks contra node_modules ni archivos generados.
  • El hook local asume que jq está instalado en la máquina del desarrollador. Documenta ese requisito, o cambia a un hook en contenedor.

Si el proyecto ya usa Husky y lint-staged, combínalos directamente:

{
  "lint-staged": {
    "configs/*.json": ["jq empty", "prettier --check"]
  }
}

Recuerda que lint-staged pasa una lista de archivos en staging, así que el comando debe aceptar múltiples rutas. Tanto jq empty como prettier --check lo hacen. No dejes que un hook escanee todo el repositorio; los commits se volverán notablemente más lentos.

6. GitHub Actions: un pipeline que puedes copiar

El workflow siguiente realiza validación de sintaxis, validación de schema y conserva el informe en caso de fallo:

name: json-validate

on:
  push:
    paths:
      - 'schemas/**'
      - 'fixtures/**'
  pull_request:

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Syntax check
        run: |
          find fixtures -name '*.json' -print0 | xargs -0 -n1 jq empty
      - name: Schema check
        run: |
          npx --yes ajv-cli validate \
            -s schemas/order.json \
            -d 'fixtures/orders/*.json' \
            --strict=false \
            --errors=text

El filtro paths evita que cambios solo de documentación disparen la validación. npx --yes encaja con repositorios sin dependencias fijadas; los proyectos de producción deberían añadir ajv-cli a devDependencies y fijar la versión. Sin un lock, una release upstream que cambie el draft o el modo estricto predeterminado puede poner el pipeline en rojo de la noche a la mañana.

7. Localización del error: de números de línea a rutas de campo

La mitad del valor de un validador es la detección; la otra mitad es la localización. Aprende a leer tres tipos de salida:

  1. Errores de sintaxis: jq reporta línea y columna, a menudo con un fragmento cercano. Arregla primero corchetes y comas; no sospeches de la codificación de inmediato.
  2. Errores de schema: el instancePath de Ajv apunta a la ubicación de los datos (por ejemplo /items/0/price), schemaPath apunta a la regla (por ejemplo #/properties/items/items/properties/price/type) y keyword nombra la restricción violada.
  3. Errores de formato: cuando falla una comprobación de format, el mensaje a menudo solo dice "must match format "email"". Combínalo con instancePath para identificar el campo.

Pega instancePath en el registro de investigación; es mucho más útil que un muro de JSON. En arrays, el índice numérico también indica qué registro falló. Si la clave de negocio es más intuitiva, añade después una línea de mapeo, como items[0].id = ORD-042.

Los errores de schema comunes, como abusar de additionalProperties: false o escribir ramas oneOf que en realidad se solapan, se tratan en Errores comunes al generar JSON Schema. Una regla de validación incorrecta es más peligrosa que ninguna validación, porque crea una falsa sensación de seguridad.

8. Tabla de selección de herramientas

Necesidad Herramienta recomendada Por qué Ojo con
Solo sintaxis jq empty Cero dependencias, rápido, códigos de salida claros Espacios en nombres de archivo durante ejecuciones por lotes
Errores de línea/columna más amigables jsonlint Salida legible Actividad de mantenimiento limitada
Validación con JSON Schema ajv-cli Ecosistema maduro, errores estructurados Alinear versiones de draft y cargar soporte de format
Repositorio mixto JSON y YAML check-jsonschema Multiformato, compatible con pre-commit Fuente del schema y acceso a red
Feedback rápido antes del commit pre-commit / lint-staged Solo procesa archivos en staging Los hooks deben aceptar múltiples rutas
Formato consistente Prettier Ampliamente adoptado Usa --check cuando no quieras reescribir archivos

Una recomendación final: verifica que el pipeline realmente falla alimentándolo primero con un registro deliberadamente inválido. El mayor riesgo no es una configuración complicada; es una configuración que nunca llega a aplicarse. Tratar "puede fallar" como criterio de aceptación es más fiable que tratar "pasa" como tal. Para la cadena de herramientas de línea de comandos más amplia, continúa con Editores, línea de comandos y automatización.

FAQ

¿Debo usar jq empty o jsonlint?

Prefiere jq empty en CI: cero dependencias, inicio rápido y semántica clara de códigos de salida. Usa jsonlint cuando necesites mensajes de línea/columna más amigables o estés investigando localmente. Ambos realizan solo validación de sintaxis; ninguno comprueba la estructura de los campos.

¿Por qué ajv reporta una palabra clave desconocida?

Primero confirma que el draft de $schema del schema coincide con la versión que usa la CLI. Muchos errores de "palabra clave desconocida" son palabras clave de un draft nuevo encontradas por un compilador antiguo. Si las versiones coinciden, busca una errata. --strict=false puede mantener funcionando schemas heredados, pero no dependas de ello a largo plazo para ocultar problemas reales.

La validación de schema pasa, pero la API sigue fallando. ¿Por qué?

Un schema valida la estructura, no la semántica de negocio. Por ejemplo, status puede ser una cadena con un enum legal, mientras que la máquina de estados prohíbe pasar directamente de paid a refunded. Reglas así pertenecen al código o a restricciones de base de datos, no a JSON Schema.

¿Cómo localizo rápidamente el campo que falla?

Lee el instancePath o el número de línea de la salida de la herramienta. El instancePath de Ajv es un JSON Pointer que apunta directamente a los datos; check-jsonschema reporta archivo y línea. Registra la ruta y mapéala a la clave de negocio; localizar el problema suele llevar minutos.

¿Debería pre-commit ejecutar una validación completa?

No. pre-commit debería procesar solo archivos en staging para mantener rápidos los commits. Los escaneos completos pertenecen a CI o a trabajos programados. La división es simple: las ejecuciones locales favorecen la velocidad, CI favorece la exhaustividad.