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.

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→1et3, excluant le2imbriqué.
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
testde 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 :
- La syntaxe tuple a changé : en draft-07 un
itemsvalué en tableau exprimait un tuple ; 2020-12 a déplacé cela versprefixItems; $refet mots-clés frères : en draft-07 les mots-clés frères sont ignorés quand$refest 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 :
- 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 ;
- Transformez le Pointer en langage clair avant de le montrer aux utilisateurs finaux ;
/items/2/priceest 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, sinonformat: "email"est effectivement inerte ; - Certains validateurs traitent
formatcomme une assertion activée par défaut, donc le comportement est incohérent ; - Même activée, la regex
emailest très laxiste —a@bpasse.
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 |