JSON Toolkit

FAQ interrogation et validation : JSONPath et Schema

L’essentiel : Les requêtes de chemin doivent-elles utiliser JSONPath ou Pointer, comment choisir entre Patch et Merge Patch, ce que Schema peut bloquer, et comment choisir une version de draft — tout expliqué en une passe.

Intermédiaire~12 min de lecturePublié le 2026-10-30Mis à jour le 2026-10-30
Illustration de la FAQ interrogation et validation

L'essentiel : utilisez JSONPath pour trouver des nœuds par condition, JSON Pointer ou JSON Patch pour adresser et éditer des emplacements exacts, et Merge Patch pour fusionner de la configuration ; épinglez les nouveaux Schemas sur 2020-12 et activez allErrors pour que chaque échec revienne en une passe.

Interroger, c'est « trouver » ; valider, c'est « confirmer que c'est correct ». Chacun a sa propre norme, donc ne les mélangez pas.

1. Requêtes de chemin

Comment choisir entre JSONPath et JSON Pointer ?

Cela dépend si vous « cherchez par condition » ou « récupérez par position ».

Dimension JSONPath JSON Pointer
Norme RFC 9535 (2024) RFC 6901 (2013)
Expression $.users[?(@.age > 18)].id /users/0/id
Jokers et récursion Prend en charge *, .. Non pris en charge
Filtrage conditionnel Pris en charge Non pris en charge
Résultat Un ensemble de nœuds (parfois plusieurs) Un emplacement unique
Expressivité Forte, mais avec un lourd héritage de différences entre implémentations Faible, mais entièrement déterministe
Usage typique Extraction de journaux, débogage, exploration de données JSON Patch, adressage de configuration, chemins d'erreur

La règle de sélection est simple :

  • Besoin de « récupérer tous les nœuds correspondant à une condition » → JSONPath ;
  • Besoin de « fixer un emplacement exact et l'ajouter, le retirer ou le modifier » → JSON Pointer ;
  • Les utiliser ensemble : trouver le nœud avec JSONPath et décrire son emplacement avec Pointer.

N'utilisez pas JSONPath pour l'adressage dans un contrat d'API. JSONPath renvoie un ensemble, et les trois branches pour les résultats vide, à un élément et à plusieurs éléments compliquent le protocole ; Pointer signifie « soit il pointe un emplacement existant, soit il n'existe pas », ce qui convient mieux à un contrat.

Quelle est la différence entre $.. et $.* ?

$.. est la descente récursive et parcourt chaque niveau ; $.* renvoie seulement les enfants directs du niveau courant.

{
  "a": { "id": 1, "child": { "id": 2 } },
  "b": { "id": 3 }
}
  • $.* → les deux objets {id:1, child:{...}} et {id:3} ;
  • $..id → trois valeurs : 1, 2, 3 ;
  • $.*.id → 1 et 3, excluant le 2 imbriqué.

La descente récursive est pratique mais coûteuse : $.. parcourt tout l'arbre, ce qui est O(n) sur un grand document, et elle ramasse facilement des champs profonds que vous ne vouliez pas. Si un niveau concret suffit, évitez ...

Les filtres peuvent-ils comparer des chaînes ? Peuvent-ils faire correspondre avec une regex ?

La comparaison de chaînes fonctionne ; la regex dépend de l'implémentation.

Syntaxe standard (RFC 9535) :

$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)]      // compare with another node

La comparaison de chaînes utilise des guillemets doubles (les guillemets simples sont aussi autorisés dans la RFC 9535, mais certaines implémentations plus anciennes n'acceptent que les guillemets doubles). La comparaison numérique suit la sémantique numérique ; mélanger une chaîne avec un nombre, comme "10" > 9, est un comportement indéfini dans la norme, donc ne vous y fiez pas.

Pour la regex, la RFC 9535 définit les fonctions match() et search(), mais leur prise en charge varie considérablement entre implémentations. L'approche sûre : laissez JSONPath faire un filtrage grossier, et faites la correspondance regex dans le code après avoir récupéré les résultats.

Faut-il utiliser JSON Patch ou Merge Patch ?

Les deux décrivent « comment changer quelque chose », mais leur expressivité et leur sémantique sont complètement différentes.

Dimension JSON Patch (RFC 6902) Merge Patch (RFC 7386)
Forme Un tableau d'opérations [{"op":"add",...}] Un objet partiel
Opérations prises en charge add / remove / replace / move / copy / test Fusion seulement (suppression via null)
Peut adresser des éléments de tableau Oui, par index Non, tout le tableau est remplacé
Atomicité test permet le verrouillage optimiste Aucune
Taille du document Grand mais précis Petit mais expressivité limitée
Bon pour Édits fins, mises à jour partielles d'API Fusion de configuration, soumission partielle de formulaire

Critères de décision :

  • Changer un champ du troisième élément d'un tableau → JSON Patch ;
  • Fusionner plusieurs entrées de configuration dans la configuration existante → Merge Patch ;
  • Obtenir un contrôle de concurrence du type « échouer si la valeur originale n'est pas X » → le test de JSON Patch.

Notez le piège de la sémantique null de Merge Patch : null signifie « supprimer cette clé », pas « mettre la valeur à null ». Mettre une valeur à null est impossible avec Merge Patch.

2. Validation de schéma

Quels problèmes JSON Schema résout-il ?

Il transforme « à quoi les données devraient ressembler » d'un accord verbal en contrat vérifiable par machine. Les erreurs qu'il peut réellement bloquer :

  • Champs obligatoires manquants (required) ;
  • Type faux, par exemple une chaîne là où un nombre est attendu (type) ;
  • Valeurs hors plage (minimum / maximum / minLength) ;
  • Valeurs hors de l'ensemble autorisé (enum) ;
  • Types d'éléments de tableau incohérents ou longueur de tableau fausse (items / minItems) ;
  • Champs manquants dans des structures imbriquées (validation récursive via properties) ;
  • Champs superflus (additionalProperties: false).

Ce qu'il ne peut pas résoudre : les règles métier (« l'heure de fin doit être postérieure à l'heure de début »), les conditions inter-champs (certaines peuvent s'exprimer avec if/then, mais la lisibilité souffre), l'unicité en base de données, et les vérifications de permissions.

Donc Schema est le gardien de la « forme des données », pas un remplacement de la logique métier.

Faut-il utiliser draft-07 ou 2020-12 ?

Utilisez 2020-12 pour les nouveaux projets ; pour les systèmes existants, vérifiez ce que le validateur prend en charge.

Version Statut Changements clés
draft-04 Obsolète Beaucoup de bibliothèques plus anciennes le prennent encore par défaut
draft-07 Norme de facto Support le plus large, écosystème le plus riche
2019-09 Transitoire Introduit $defs, unevaluatedProperties
2020-12 Recommandé actuellement items scindé en prefixItems + items, prend en charge $dynamicRef

Deux pièges de migration :

  1. La syntaxe tuple a changé : en draft-07 un items valué en tableau exprimait un tuple ; 2020-12 a déplacé cela vers prefixItems ;
  2. $ref et mots-clés frères : en draft-07 les mots-clés frères sont ignorés quand $ref est présent ; 2020-12 permet qu'ils coexistent.

Si le validateur ne prend en charge que draft-07, écrire quand même de la syntaxe 2020-12 est silencieusement ignoré — et le cas le plus dangereux est « la validation passe » alors que rien n'a été validé du tout.

Comment générer des types TypeScript depuis un Schema ?

Deux voies : dériver les types du Schema, ou générer le Schema depuis les types.

Du Schema vers les types (le backend définit le contrat, le frontend le consomme) :

import type { FromSchema } from 'json-schema-to-ts';

const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'string' },
    age: { type: 'integer' },
  },
  required: ['id'],
  additionalProperties: false,
} as const;

type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }

La clé est as const : sans lui, type: 'string' est inféré comme string plutôt que le littéral 'string', et l'inférence s'effondre.

Des types vers le Schema (le frontend a déjà des types et a besoin de validation à l'exécution) :

import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);

L'avantage de cette voie est que « une définition produit à la fois les types et la validation à l'exécution » ; le coût est d'introduire un DSL, et le Schema généré peut porter des détails d'implémentation (par exemple une structure $ref peu attrayante).

Comment localiser le champ précis quand la validation échoue ?

Regardez l'instancePath (2020-12) ou dataPath (draft-07) renvoyé par le validateur ; c'est un JSON Pointer vers l'emplacement de l'erreur.

[
  {
    "instancePath": "/items/2/price",
    "schemaPath": "#/properties/items/items/properties/price/type",
    "keyword": "type",
    "message": "must be number"
  }
]

instancePath: "/items/2/price" vous dit directement : le champ price du troisième élément du tableau items a le mauvais type.

Deux suggestions pratiques :

  1. Retournez toutes les erreurs d'un coup au lieu de lever à la première — le frontend corrigeant tout en une passe est plus rapide que des tentatives aller-retour ;
  2. Transformez le Pointer en langage clair avant de le montrer aux utilisateurs finaux ; /items/2/price est utile aux développeurs et inutile aux utilisateurs.

allErrors: true est l'interrupteur dans la plupart des validateurs (Ajv exige de l'activer explicitement) ; le défaut est souvent le mode fail-fast.

Faut-il activer additionalProperties: false ?

Activez-le pour les contrats internes ; soyez prudent avec les API publiques.

Une fois activé, tout champ non déclaré sous properties échoue à la validation. Le bénéfice est d'attraper les fautes de frappe (userNmae) et les pass-through inattendus ; l'inconvénient est qu'un ajout de champ amont casse les consommateurs aval, détruisant la compatibilité descendante.

Compromis :

Scénario Recommandation
Entre services internes Activer. Les changements de champ passent par la gestion de version
API externes publiques Désactiver. Permettre à l'amont d'ajouter des champs
Consommation de données tierces Désactiver. Vous ne contrôlez pas l'autre côté
Validation de configuration Activer. Une faute de frappe dans la config devrait échouer immédiatement

Comment valider que les éléments de tableau sont uniques ?

Deux sémantiques différentes — ne les confondez pas :

{ "uniqueItems": true }

Cela déduplique l'élément entier, en exigeant que le tableau ne contienne pas deux éléments profondément égaux.

{
  "type": "array",
  "items": { "type": "object", "properties": { "id": { "type": "string" } } },
  "uniqueItems": true
}

Le fragment ci-dessus garantit seulement que les objets dans leur ensemble ne sont pas dupliqués ; {"id":"a","name":"x"} et {"id":"a","name":"y"} ne comptent pas comme des doublons.

Pour dédupliquer par un champ précis, Schema ne peut pas l'exprimer ; il faut valider dans la couche métier :

const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
  throw new Error('id 重复');
}

format valide-t-il réellement les e-mails et les dates ?

Pas nécessairement. Dans la spécification, format est une « annotation » par défaut plutôt qu'une « assertion », donc un validateur peut la collecter sans la valider.

  • Ajv exige d'installer et configurer le paquet séparé ajv-formats, sinon format: "email" est effectivement inerte ;
  • Certains validateurs traitent format comme une assertion activée par défaut, donc le comportement est incohérent ;
  • Même activée, la regex email est très laxiste — a@b passe.

Conclusion : ne traitez pas format comme une validation forte. Faites la validation stricte des e-mails, URL et dates dans votre code métier ; garder format dans le Schema comme documentation vaut le coup, mais n'attendez pas qu'il bloque les données sales.

3. Aide-mémoire

Question Conclusion
Trouver des nœuds par condition JSONPath
Adressage précis, appliquer des patchs JSON Pointer
Changer des éléments de tableau JSON Patch, Merge Patch ne peut pas
Fusionner de la configuration Merge Patch
Version de Schema pour nouveaux projets 2020-12, attention à prefixItems
Générer des types TS depuis un Schema json-schema-to-ts + as const
Localiser les erreurs de validation Lire instancePath, activer allErrors
Dédupliquer par champ Schema ne peut pas ; utiliser un Set dans la couche métier
format: email La plupart des validateurs ne le vérifient pas par défaut, donc ne vous y fiez pas