JSON Toolkit

Validazione JSON in CI: una pipeline con jsonlint, ajv e jq

In sintesi: La validazione sintattica prova che un file viene analizzato; la validazione dello schema prova che i campi rispettano il contratto. In CI ciò che conta sono codici di uscita non ambigui, posizioni degli errori e percorsi di riparazione.

Intermedio~10 min di letturaPubblicato il 2026-10-31Aggiornato il 2026-10-31
Copertina della validazione JSON in CI

In sintesi: in CI lascia che jq empty gestisca la sintassi, ajv-cli la struttura (check-jsonschema per team multi-formato) e Prettier lo stile; incatenali per livello e fermati al primo errore.

Il parser del tuo editor locale viene superato non significa che il file appartenga alla CI; la CI viene superata non significa nemmeno che i campi rispettino il contratto.

1. Separare la validazione sintattica da quella dello schema

L'espressione "validazione JSON" è usata fin troppo ampiamente, ed è per questo che i team scelgono spesso un unico strumento per risolvere due problemi diversi:

Livello Domanda a cui risponde Strumenti tipici Cosa significa un errore
Sintassi Questo file è JSON valido? jq empty, jsonlint, python -m json.tool Parentesi, virgole, virgolette o codifica sono rotte
Schema Campi, tipi e proprietà obbligatorie rispettano il contratto? ajv-cli, check-jsonschema Il file viene analizzato, ma la struttura di business è sbagliata
Stile Indentazione e ordine delle chiavi sono coerenti? Prettier, jq -S La formattazione è incoerente e i diff sono rumorosi

Questi livelli non possono sostituirsi a vicenda. Un file che passa la sintassi può comunque fallire lo schema: {"price": "12.5"} è JSON valido, ma se lo schema richiede che price sia un numero, non è accettabile. Al contrario, per quanto completo sia lo schema, un file privo di una graffa di chiusura non lo compilerà mai.

Quindi la pipeline dovrebbe eseguire prima la sintassi, poi la struttura e infine lo stile. Fermati non appena un livello fallisce, altrimenti un errore di parsing si maschera da errore di campo.

2. Livello sintattico: jq empty è l'opzione con meno dipendenze

Se jq è già nell'immagine della CI, la validazione sintattica non costa quasi nulla:

jq empty data.json

jq empty non produce output; analizza solo il file. Esce con 0 in caso di successo e con un codice diverso da zero più le informazioni di riga/colonna in caso di errore. Questo lo rende più leggero che tirarsi dietro un intero albero di dipendenze Node, e un buon primo cancello.

Quando si validano in batch i file JSON di un repository, escludi node_modules, l'output di build e le cache di terze parti:

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

-print0 e xargs -0 gestiscono gli spazi nei nomi dei file, mentre -n1 garantisce un file per invocazione così il messaggio di errore identifica il colpevole. Non passare centinaia di file a un unico processo; i messaggi di errore si sovrascriverebbero a vicenda.

jsonlint è un'altra scelta comune, di solito invocato come npx jsonlint -q data.json, dove -q stampa solo gli errori. Riporta numeri di riga e colonna, ma la sua manutenzione è meno attiva di jq, e versioni diverse tollerano BOM e commenti in modo diverso. Trattalo come un controllore di sintassi più leggibile, non come un validatore di schema.

3. Livello dello schema: le basi di ajv-cli validate

Ajv è il validatore di schema più usato nell'ecosistema JavaScript, e ajv-cli lo espone come strumento da riga di comando:

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

-s punta al file dello schema e -d ai dati da validare. In caso di errore il processo esce con un codice diverso da zero, quindi la CI fallisce subito. I progetti reali di solito aggiungono due flag:

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

--strict=false aiuta con gli schemi più vecchi: la modalità strict tratta le parole chiave sconosciute e le dichiarazioni required sospette come errori, cosa che molti schemi legacy non superano. --errors=text produce output leggibile per gli umani, mentre la struttura JSON predefinita è migliore per le macchine. Scegli in base a chi consuma il risultato.

Deve corrispondere anche la versione del draft dello schema. Le versioni major di Ajv supportano draft diversi per impostazione predefinita, e ajv-cli può sceglierne uno con --spec=draft7, --spec=draft2019 o --spec=draft2020. Se uno schema dichiara $schema: "https://json-schema.org/draft/2020-12/schema" ma la CLI lo compila come draft-07, alcune parole chiave vengono ignorate silenziosamente. Per saperne di più sulla stesura degli schemi e sulle differenze tra versioni, vedi Introduzione a JSON Schema.

La validazione dei formati è un'altra esigenza frequente. La parola chiave format è solo annotativa per impostazione predefinita; per far sì che email, date-time e uri impongano davvero qualcosa, carica ajv-formats:

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

Questo passaggio è facile da dimenticare, e il risultato è che un campo email accetta not-an-email. Prima di rilasciare, fornisci alla pipeline un record deliberatamente non valido; questo prova che la regola è attiva molto meglio che leggere dieci volte la documentazione.

check-jsonschema: più formati e errori più amichevoli

Se il team gestisce YAML, TOML o configurazioni di GitHub Actions oltre a JSON, check-jsonschema è più comodo:

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

I suoi vantaggi pratici:

  1. Supporto nativo per YAML, che si adatta ai file "in forma JSON ma scritti in YAML", come i manifest di Kubernetes e le configurazioni di CI.
  2. Gli errori includono percorso del file, numero di riga e percorso del campo, mantenendo bassi i costi di localizzazione.
  3. È fornito un hook pre-commit, quindi l'integrazione costa poco.
  4. Può recuperare gli schemi comuni da SchemaStore, ma accetta anche file locali, evitando una dipendenza dalla rete.

Nei team cross-linguaggio Python è spesso già presente, quindi installare e aggiornare check-jsonschema è più leggero che mantenere una toolchain Node. Lascia che jq gestisca la sintassi e questo strumento la struttura; la combinazione ha costi di manutenzione molto bassi.

4. I codici di uscita sono l'unico segnale affidabile per la CI

Per quanto curato sia l'output, la CI legge solo i codici di uscita. Tre trappole sono comuni:

Sintomo Causa Correzione
Ci sono errori ma la CI è verde Il comando termina con || true Rimuovi il fallback, o usalo solo dove è esplicitamente consentito
Nessun errore ma la CI è rossa diff tratta "differenze trovate" come errore Distingui "differenza" da "errore di esecuzione"
L'output degli errori è troncato L'output parallelo di più file si sovrascrive Esegui in serie o stampa per file

Ricorda la semantica dei codici di uscita di jq: il parsing normale restituisce 0; con -e, un risultato dell'espressione false o null restituisce 1; errori d'uso e file mancanti restituiscono 2; errori di compilazione dello script restituiscono 3. Gli strumenti di schema usano in genere 0 per il successo, 1 per l'errore di validazione e 2 per errori dello strumento o degli argomenti. Quando scrivi un wrapper, non comprimere ogni codice diverso da zero in "i dati sono sbagliati", altrimenti un errore d'ambiente verrà diagnosticato come problema di contenuto.

5. pre-commit: intercetta i problemi prima del commit

Il valore del pre-commit non è "un controllo in più"; è dare agli sviluppatori un feedback in un minuto invece che dopo un'esecuzione CI di dieci minuti. Una configurazione minima è questa:

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$

Due dettagli contano:

  • files dovrebbe corrispondere solo alle directory che richiedono la validazione. Non eseguire gli hook su node_modules o sui file generati.
  • L'hook locale presuppone che jq sia installato sulla macchina dello sviluppatore. Documenta quel requisito, oppure passa a un hook containerizzato.

Se il progetto usa già Husky e lint-staged, combinali direttamente:

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

Ricorda che lint-staged passa un elenco di file in staging, quindi il comando deve accettare più percorsi. Sia jq empty sia prettier --check lo fanno. Non lasciare che un hook scansioni l'intero repository; i commit diventeranno sensibilmente più lenti.

6. GitHub Actions: una pipeline da copiare

Il workflow seguente esegue la validazione sintattica, quella dello schema e conserva il report in caso di errore:

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

Il filtro paths evita che le modifiche alla sola documentazione inneschino la validazione. npx --yes si adatta ai repository senza dipendenze fissate; i progetti di produzione dovrebbero aggiungere ajv-cli a devDependencies e fissare la versione. Senza lock, un rilascio upstream che cambia il draft predefinito o la modalità strict può rendere la pipeline rossa da un giorno all'altro.

7. Localizzazione degli errori: dai numeri di riga ai percorsi dei campi

Metà del valore di un validatore è la rilevazione; l'altra metà è la localizzazione. Impara a leggere tre tipi di output:

  1. Errori di sintassi: jq riporta riga e colonna, spesso con uno snippet vicino. Correggi prima parentesi e virgole; non sospettare subito la codifica.
  2. Errori di schema: instancePath di Ajv punta alla posizione dei dati (per esempio /items/0/price), schemaPath punta alla regola (per esempio #/properties/items/items/properties/price/type), e keyword nomina il vincolo violato.
  3. Errori di formato: quando un controllo format fallisce, il messaggio spesso dice solo "must match format "email"". Abbinalo a instancePath per identificare il campo.

Incolla instancePath nel registro di indagine; è molto più utile di un muro di JSON. Per gli array, l'indice numerico ti dice anche quale record ha fallito. Se la chiave di business è più intuitiva, aggiungi dopo una riga di mappatura, come items[0].id = ORD-042.

Gli errori di schema comuni, come abusare di additionalProperties: false o scrivere rami oneOf che in realtà si sovrappongono, sono trattati in Trappole comuni nella generazione di JSON Schema. Una regola di validazione sbagliata è più pericolosa dell'assenza di validazione, perché crea un falso senso di sicurezza.

8. Tabella di scelta degli strumenti

Esigenza Strumento consigliato Perché Attenzione a
Solo sintassi jq empty Zero dipendenze, veloce, codici di uscita chiari Spazi nei nomi dei file durante l'esecuzione in batch
Errori riga/colonna più amichevoli jsonlint Output leggibile L'attività di manutenzione è limitata
Validazione JSON Schema ajv-cli Ecosistema maturo, errori strutturati Allinea le versioni dei draft e carica il supporto format
Repository misto JSON e YAML check-jsonschema Multi-formato, amichevole con pre-commit Fonte dello schema e accesso alla rete
Feedback rapido prima del commit pre-commit / lint-staged Elabora solo i file in staging Gli hook devono accettare più percorsi
Formattazione coerente Prettier Ampiamente adottato Usa --check quando non vuoi riscrivere i file

Un'ultima raccomandazione: verifica che la pipeline fallisca davvero fornendole prima un record deliberatamente non valido. Il rischio più grande non è una configurazione complicata; è una configurazione che non entra mai in vigore. Trattare "può fallire" come criterio di accettazione è più affidabile che trattare "passa" come tale. Per la toolchain più ampia da riga di comando, continua con Editor, riga di comando e automazione.

FAQ

Devo usare jq empty o jsonlint?

Preferisci jq empty in CI: zero dipendenze, avvio veloce e semantica chiara dei codici di uscita. Usa jsonlint quando ti servono messaggi di riga/colonna più amichevoli o stai indagando in locale. Entrambi eseguono solo la validazione sintattica; nessuno dei due controlla la struttura dei campi.

Perché ajv riporta una parola chiave sconosciuta?

Per prima cosa conferma che il draft $schema dello schema corrisponda alla versione usata dalla CLI. Molti errori di "parola chiave sconosciuta" sono parole chiave di un nuovo draft incontrate da un compilatore più vecchio. Se le versioni corrispondono, cerca un errore di battitura. --strict=false può mantenere funzionanti gli schemi legacy, ma non affidarti a lungo a questa opzione per nascondere problemi reali.

La validazione dello schema passa, ma l'API fallisce comunque. Perché?

Uno schema valida la struttura, non la semantica di business. Per esempio, status può essere una stringa con un enum legale, mentre la macchina a stati vieta il passaggio diretto da paid a refunded. Regole come questa appartengono al codice o ai vincoli del database, non a JSON Schema.

Come localizzo rapidamente il campo che fallisce?

Leggi instancePath o il numero di riga dall'output dello strumento. instancePath di Ajv è un JSON Pointer che punta direttamente ai dati; check-jsonschema riporta file e riga. Registra il percorso e mappalo alla chiave di business; localizzare il problema di solito richiede pochi minuti.

Il pre-commit deve eseguire una validazione completa?

No. Il pre-commit dovrebbe elaborare solo i file in staging per mantenere veloci i commit. Le scansioni complete appartengono alla CI o ai job pianificati. La divisione è semplice: l'esecuzione locale favorisce la velocità, la CI favorisce la completezza.