JSON Toolkit

Validation JSON en CI : un pipeline avec jsonlint, ajv et jq

L’essentiel : La validation syntaxique prouve qu'un fichier parse ; la validation de schéma prouve que les champs suivent le contrat. En CI, ce qui compte, ce sont des codes de sortie non ambigus, des localisations d'erreur et des chemins de réparation.

Intermédiaire~10 min de lecturePublié le 2026-10-31Mis à jour le 2026-10-31
Illustration de la validation JSON en CI

L'essentiel : en CI, laissez jq empty gérer la syntaxe, ajv-cli gérer la structure (check-jsonschema pour les équipes multi-format), et Prettier gérer le style ; enchaînez-les par couche et arrêtez-vous au premier échec.

Passer le parseur dans votre éditeur local ne signifie pas que le fichier appartient à la CI ; passer la CI ne signifie pas non plus que les champs correspondent au contrat.

1. Séparez la validation syntaxique de la validation de schéma

L'expression « validation JSON » est utilisée bien trop largement, c'est pourquoi les équipes choisissent souvent un outil pour résoudre deux problèmes différents :

Couche Question à laquelle elle répond Outils typiques Ce que signifie un échec
Syntaxe Ce fichier est-il du JSON valide ? jq empty, jsonlint, python -m json.tool Crochets, virgules, guillemets ou encodage sont cassés
Schéma Les champs, types et propriétés obligatoires correspondent-ils au contrat ? ajv-cli, check-jsonschema Le fichier parse, mais la structure métier est fausse
Style L'indentation et l'ordre des clés sont-ils cohérents ? Prettier, jq -S Le formatage est incohérent et les diffs sont bruyants

Ces couches ne peuvent pas se remplacer. Un fichier qui passe la syntaxe peut encore échouer au schéma : {"price": "12.5"} est du JSON valide, mais si le schéma exige que price soit un nombre, il n'est pas acceptable. Inversement, aussi complet que soit le schéma, un fichier auquel il manque une accolade fermante ne compilera jamais contre lui.

Donc le pipeline devrait exécuter la syntaxe d'abord, la structure ensuite, et le style en dernier. Arrêtez-vous dès qu'une couche échoue, sinon une erreur de parsing se déguise en erreur de champ.

2. Couche syntaxe : jq empty est l'option à dépendance minimale

Si jq est déjà dans l'image CI, la validation syntaxique ne coûte presque rien :

jq empty data.json

jq empty ne produit aucune sortie ; il parse seulement le fichier. Il sort avec 0 en cas de succès et un code non nul plus des informations de ligne/colonne en cas d'échec. Cela le rend plus léger que de tirer tout un arbre de dépendances Node, et c'est une bonne première barrière.

Lors d'une validation par lots de fichiers JSON dans un dépôt, excluez node_modules, la sortie de build et les caches tiers :

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

-print0 et xargs -0 gèrent les espaces dans les noms de fichiers, tandis que -n1 garantit un fichier par invocation pour que le message d'erreur identifie le coupable. Ne passez pas des centaines de fichiers à un seul processus ; les messages d'erreur s'écraseraient.

jsonlint est un autre choix courant, généralement invoqué comme npx jsonlint -q data.json, où -q n'imprime que les erreurs. Il rapporte des numéros de ligne et colonne, mais sa maintenance est moins active que jq, et différentes versions tolèrent différemment le BOM et les commentaires. Traitez-le comme un vérificateur de syntaxe plus lisible, pas comme un validateur de schéma.

3. Couche schéma : les bases de ajv-cli validate

Ajv est le validateur de schéma le plus largement utilisé dans l'écosystème JavaScript, et ajv-cli l'expose comme outil en ligne de commande :

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

-s pointe le fichier de schéma et -d les données à valider. En cas d'échec le processus sort non nul, donc la CI échoue immédiatement. Les vrais projets ajoutent généralement deux options :

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

--strict=false aide avec les anciens schémas : le mode strict traite les mots-clés inconnus et les déclarations required suspectes comme des erreurs, ce que beaucoup de schémas hérités échouent. --errors=text produit une sortie lisible par l'humain, tandis que la structure JSON par défaut convient mieux aux machines. Choisissez selon qui consomme le résultat.

La version de draft du schéma doit aussi correspondre. Les versions majeures d'Ajv prennent en charge différents drafts par défaut, et ajv-cli peut en sélectionner un avec --spec=draft7, --spec=draft2019 ou --spec=draft2020. Si un schéma déclare $schema: "https://json-schema.org/draft/2020-12/schema" mais que le CLI le compile comme draft-07, certains mots-clés sont silencieusement ignorés. Pour en savoir plus sur la rédaction de schémas et les différences de version, voir Premiers pas avec JSON Schema.

La validation de format est une autre exigence fréquente. Le mot-clé format est une annotation seule par défaut ; pour que email, date-time et uri appliquent réellement quelque chose, chargez ajv-formats :

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

Cette étape est facile à manquer, et le résultat est qu'un champ email accepte not-an-email. Avant de livrer, fournissez au pipeline un enregistrement délibérément invalide ; cela prouve que la règle est active bien mieux que de lire dix fois la documentation.

check-jsonschema : formats multiples et erreurs plus conviviales

Si l'équipe traite du YAML, du TOML ou de la configuration GitHub Actions en plus du JSON, check-jsonschema est plus pratique :

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

Ses avantages pratiques :

  1. Support YAML natif, qui convient aux fichiers « en forme de JSON mais écrits en YAML », comme les manifests Kubernetes et la configuration CI.
  2. Les erreurs incluent le chemin de fichier, le numéro de ligne et le chemin de champ, gardant les coûts de localisation bas.
  3. Un hook pre-commit est fourni, donc l'intégration est peu coûteuse.
  4. Il peut tirer des schémas communs de SchemaStore, mais accepte aussi des fichiers locaux, évitant une dépendance réseau.

Dans les équipes multi-langages, Python est souvent déjà présent, donc installer et mettre à niveau check-jsonschema est plus léger que de maintenir une chaîne d'outils Node. Laissez jq gérer la syntaxe et cet outil gérer la structure ; la combinaison a une surcharge de maintenance très faible.

4. Les codes de sortie sont le seul signal CI fiable

Peu importe la beauté de la sortie, la CI ne lit que les codes de sortie. Trois pièges sont courants :

Symptôme Cause Correction
Des erreurs existent mais la CI est verte La commande finit par || true Supprimez le repli, ou utilisez-le seulement là où c'est explicitement autorisé
Aucune erreur mais la CI est rouge diff traite « différences trouvées » comme un échec Distinguez « différence » de « erreur d'exécution »
La sortie d'erreur est tronquée La sortie parallèle multi-fichiers s'écrase elle-même Exécutez en série ou imprimez par fichier

Retenez la sémantique des codes de sortie de jq : le parsing normal renvoie 0 ; avec -e, un résultat d'expression false ou null renvoie 1 ; les erreurs d'usage et les fichiers manquants renvoient 2 ; les erreurs de compilation de script renvoient 3. Les outils de schéma utilisent généralement 0 pour le succès, 1 pour un échec de validation, et 2 pour des erreurs d'outil ou d'argument. Lors de l'écriture d'un wrapper, ne réduisez pas chaque code non nul à « les données sont fausses », sinon un échec d'environnement sera diagnostiqué à tort comme un problème de contenu.

5. pre-commit : attraper les problèmes avant le commit

La valeur de pre-commit n'est pas « une vérification de plus » ; c'est de donner aux développeurs un retour en une minute au lieu d'après dix minutes de CI. Une configuration minimale ressemble à ceci :

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$

Deux détails comptent :

  • files devrait correspondre seulement aux répertoires qui ont besoin de validation. N'exécutez pas de hooks sur node_modules ou des fichiers générés.
  • Le hook local suppose que jq est installé sur la machine du développeur. Documentez cette exigence, ou passez à un hook conteneurisé.

Si le projet utilise déjà Husky et lint-staged, combinez-les directement :

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

Rappelez-vous que lint-staged passe une liste de fichiers indexés, donc la commande doit accepter plusieurs chemins. jq empty et prettier --check le font tous deux. Ne laissez pas un hook scanner tout le dépôt ; les commits deviendront nettement plus lents.

6. GitHub Actions : un pipeline copiable

Le workflow ci-dessous effectue la validation syntaxique, la validation de schéma et conserve le rapport en cas d'échec :

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

Le filtre paths empêche les changements de documentation seule de déclencher la validation. npx --yes convient aux dépôts sans dépendances épinglées ; les projets de production devraient ajouter ajv-cli à devDependencies et épingler la version. Sans lock, une sortie amont qui change le draft ou le mode strict par défaut peut rendre le pipeline rouge du jour au lendemain.

7. Localisation des erreurs : des numéros de ligne aux chemins de champ

La moitié de la valeur d'un validateur est la détection ; l'autre moitié est la localisation. Apprenez à lire trois types de sortie :

  1. Erreurs de syntaxe : jq rapporte ligne et colonne, souvent avec un extrait voisin. Corrigez d'abord crochets et virgules ; ne suspectez pas immédiatement l'encodage.
  2. Erreurs de schéma : instancePath d'Ajv pointe l'emplacement des données (par exemple /items/0/price), schemaPath pointe la règle (par exemple #/properties/items/items/properties/price/type), et keyword nomme la contrainte violée.
  3. Erreurs de format : quand une vérification format échoue, le message dit souvent seulement « must match format "email" ». Combinez-le avec instancePath pour identifier le champ.

Collez instancePath dans l'enregistrement d'investigation ; il est bien plus utile qu'un mur de JSON. Pour les tableaux, l'index numérique indique aussi quel enregistrement a échoué. Si la clé métier est plus intuitive, ajoutez ensuite une ligne de correspondance, comme items[0].id = ORD-042.

Les erreurs de schéma courantes, comme l'abus de additionalProperties: false ou des branches oneOf qui se chevauchent réellement, sont couvertes dans Pièges courants de génération de JSON Schema. Une règle de validation fausse est plus dangereuse qu'aucune validation, car elle crée un faux sentiment de sécurité.

8. Tableau de sélection d'outils

Besoin Outil recommandé Pourquoi Attention
Syntaxe seulement jq empty Zéro dépendance, rapide, codes de sortie clairs Espaces dans les noms de fichiers lors des exécutions par lots
Erreurs ligne/colonne plus conviviales jsonlint Sortie lisible Activité de maintenance limitée
Validation JSON Schema ajv-cli Écosystème mature, erreurs structurées Aligner les versions de draft et charger le support de format
Dépôt mixte JSON et YAML check-jsonschema Multi-format, convivial pour pre-commit Source du schéma et accès réseau
Retour rapide avant commit pre-commit / lint-staged Ne traite que les fichiers indexés Les hooks doivent accepter plusieurs chemins
Formatage cohérent Prettier Largement adopté Utilisez --check quand vous ne voulez pas réécrire les fichiers

Une recommandation finale : vérifiez que le pipeline échoue réellement en lui fournissant d'abord un enregistrement délibérément invalide. Le plus grand risque n'est pas une configuration compliquée ; c'est une configuration qui ne prend jamais effet. Traiter « il peut échouer » comme critère d'acceptation est plus fiable que de traiter « il passe » comme tel. Pour la chaîne d'outils en ligne de commande plus large, poursuivez avec Éditeurs, ligne de commande et automatisation.

FAQ

Faut-il utiliser jq empty ou jsonlint ?

Préférez jq empty en CI : zéro dépendance, démarrage rapide et sémantique de code de sortie claire. Utilisez jsonlint quand il faut des messages ligne/colonne plus conviviaux ou que vous investiguez localement. Les deux n'effectuent qu'une validation syntaxique ; aucun ne vérifie la structure des champs.

Pourquoi ajv signale-t-il un mot-clé inconnu ?

Confirmez d'abord que le draft $schema du schéma correspond à la version utilisée par le CLI. Beaucoup d'erreurs « mot-clé inconnu » sont des mots-clés de nouveau draft rencontrés par un compilateur plus ancien. Si les versions correspondent, cherchez une faute de frappe. --strict=false peut garder des schémas hérités fonctionnels, mais ne comptez pas dessus à long terme pour masquer de vrais problèmes.

La validation de schéma passe, mais l'API échoue quand même. Pourquoi ?

Un schéma valide la structure, pas la sémantique métier. Par exemple, status peut être une chaîne avec une énumération légale, tandis que la machine à états interdit de passer directement de paid à refunded. Ces règles appartiennent au code ou aux contraintes de base de données, pas à JSON Schema.

Comment localiser rapidement le champ fautif ?

Lisez l'instancePath ou le numéro de ligne dans la sortie de l'outil. L'instancePath d'Ajv est un JSON Pointer qui pointe directement les données ; check-jsonschema rapporte fichier et ligne. Enregistrez le chemin et mappez-le vers la clé métier ; localiser le problème prend généralement quelques minutes.

pre-commit doit-il lancer une validation complète ?

Non. pre-commit devrait traiter seulement les fichiers indexés pour garder les commits rapides. Les scans complets appartiennent à la CI ou à des tâches planifiées. La division est simple : les exécutions locales privilégient la vitesse, la CI privilégie la complétude.