Testeur JSONPath
Testez des requêtes JSONPath (RFC 9535) sur du JSON : sélecteurs, filtres, les cinq fonctions et le chemin normalisé de chaque résultat — dans le navigateur.
- Chemin
$['store']['book'][0]['title']"Sayings of the Century" - Chemin
$['store']['book'][2]['title']"Moby Dick"
Un langage de requête qui a enfin un standard
JSONPath est à JSON ce que XPath est à XML : un petit langage pour désigner des parties d'un document. Vous écrivez une expression comme $.store.book[0].title et elle sélectionne les nœuds correspondants. L'idée vient d'un billet de blog de Stefan Goessner en 2007, et pendant dix-sept ans ce billet fut la seule référence — ce qui a fait que chaque bibliothèque a comblé les lacunes différemment. Que sélectionne un $.. seul ? $[1,2] revient-il forcément dans l'ordre ? Comment un filtre compare-t-il une valeur absente ? Posez la question à trois bibliothèques JSONPath et vous pourriez obtenir trois réponses.
RFC 9535, publié en 2024, a enfin tout fixé. Ce testeur vise ce standard, pas le folklore. Il exécute la requête comme le dit la RFC, vous montre chaque correspondance avec son emplacement exact et — surtout — refuse une requête valide dans un dialecte plus ancien mais pas dans RFC 9535, en vous indiquant où est le problème plutôt que de faire silencieusement quelque chose de non standard qu'un autre outil ferait autrement.
Segments et sélecteurs
Une requête est l'identifiant racine $ suivi d'une suite de segments, et chaque segment applique un ou plusieurs sélecteurs aux nœuds qu'il reçoit. Il y a cinq sélecteurs :
- Nom — $.store ou $["store"] sélectionne la valeur d'un membre. La forme avec point est un raccourci ; la forme à crochets et guillemets fonctionne pour toute clé, y compris une clé avec des espaces ou de la ponctuation.
- Joker — * sélectionne tous les membres d'un objet ou tous les éléments d'un tableau.
- Indice — [0] sélectionne un élément de tableau, et un indice négatif compte depuis la fin, donc [-1] est le dernier.
- Tranche (slice) — [start:end:step] sélectionne une plage, exactement comme en Python : [1:3] sont les éléments 1 et 2, [::-1] inverse, [::2] prend un élément sur deux.
- Filtre — [?<expression>] ne garde que les éléments ou membres pour lesquels l'expression est vraie (voir plus bas).
Un crochet peut contenir plusieurs sélecteurs à la fois : [0, 2, "title"] sélectionne trois choses en un seul segment. Et un segment peut être un segment enfant (un point ou un crochet simple) ou un segment descendant (..), qui cherche dans le nœud et tous ses descendants — $..author trouve chaque author n'importe où dans le document.
Les filtres, et les quatre façons de comparer le néant
Un sélecteur de filtre teste chaque élément avec une expression logique, dans laquelle @ désigne l'élément courant et $ le document entier. L'expression peut comparer des valeurs (==, !=, <, <=, >, >=), combiner des tests avec && et || et !, et simplement tester l'existence : $..book[[email protected]] garde les livres qui ont un ISBN, car @.isbn ne sélectionne un nœud que lorsque la clé est présente.
Le point subtil est de comparer quelque chose d'absent. Une requête à gauche ou à droite d'une comparaison produit une valeur ou rien. La RFC le définit précisément : rien égale rien, rien n'égale aucune valeur réelle, et tout test d'ordre (<, >) contre rien est simplement faux. Ainsi @.price < 10 ignore silencieusement un élément sans price plutôt que d'échouer — ce qui est généralement ce que vous voulez, et toujours ce que dit le standard.
Les cinq fonctions
RFC 9535 ajoute cinq fonctions que vous pouvez appeler dans un filtre :
- length() — la longueur d'une chaîne (comptée en caractères), d'un tableau ou d'un objet.
- count() — combien de nœuds une requête sélectionne, pour filtrer sur la cardinalité : [?count(@.chapters) > 3].
- value() — la valeur unique qu'une requête sélectionne, ou rien si elle en sélectionne zéro ou plusieurs.
- match() — si une chaîne correspond entièrement à une expression régulière.
- search() — si une expression régulière se trouve n'importe où dans une chaîne.
Le standard est strict sur leur usage, et ce testeur l'impose à l'analyse : length() prend exactement une valeur, donc length(@.*) — une requête qui pourrait sélectionner de nombreux nœuds — est une erreur de syntaxe, pas une requête qui se comporte mal en silence. De même match() renvoie vrai ou faux, donc écrire match(@.a, "x") == true est refusé, car un résultat logique n'est pas quelque chose que l'on compare.
Les expressions régulières sont I-Regexp
match() et search() n'utilisent pas les expressions régulières de JavaScript ; elles utilisent I-Regexp (RFC 9485), un petit sous-ensemble portable conçu pour se comporter de la même façon dans tous les langages. L'essentiel est ce à quoi vous vous attendez — classes de caractères, quantificateurs, alternance, propriétés Unicode comme \p{Lu} pour une lettre majuscule. Le seul piège est le point : en I-Regexp, . correspond à tout caractère sauf un retour chariot ou un saut de ligne, ce qui veut dire qu'il correspond bien aux séparateurs de ligne Unicode U+2028 et U+2029 qu'un point de JavaScript exclut. Ce testeur compile I-Regexp fidèlement, de sorte qu'un motif se comporte ici comme l'évaluerait un serveur conforme.
La différence entre les deux fonctions n'est que l'ancrage : match exige que toute la chaîne corresponde, tandis que search cherche le motif n'importe où dans celle-ci. match(@, "a.*") accepte "abc" ; search(@, "b") accepte toute chaîne contenant un b.
Chemins normalisés, et exécution dans le navigateur
Pour chaque correspondance, ce testeur affiche un Normalized Path — l'emplacement canonique que définit la RFC, écrit avec la forme à crochets et guillemets : $['store']['book'][0]['author']. Contrairement à la requête, qui peut sélectionner de nombreux nœuds, un chemin normalisé en désigne exactement un, avec seulement des sélecteurs de nom et d'indice et un style de guillemets fixe. C'est la réponse à « d'où vient cette correspondance », et c'est ce qui permet de reconvertir un résultat de joker en un ensemble d'emplacements concrets. La plupart des testeurs affichent les valeurs et vous laissent déduire les chemins ; celui-ci affiche les deux.
Tout le moteur s'exécute dans votre navigateur — le JSON est analysé et la requête évaluée sur votre propre appareil, et rien de ce que vous collez n'est téléversé, stocké ni journalisé. Il est validé contre la suite officielle de tests de conformité JSONPath, de sorte que ses réponses correspondent au standard et non à l'interprétation d'une seule bibliothèque.
Questions fréquentes
- Quel dialecte JSONPath utilise-t-il ?
- RFC 9535, le standard IETF de 2024, et il est validé contre la suite officielle de tests de conformité JSONPath. Il n'accepte volontairement pas les anciennes conventions de Goessner là où elles divergent de la RFC ; une telle requête est rejetée avec la position du problème pour que vous puissiez la corriger.
- Pourquoi ma requête a-t-elle été rejetée alors qu'elle fonctionne dans un autre outil ?
- Parce que cet outil suit les conventions de Goessner antérieures au standard, qui diffèrent de RFC 9535 en plusieurs points — un $.. seul, des expressions de script comme [(@.length-1)], des indices à zéro initial et des noms non entre guillemets avec des caractères spéciaux sont tous non standard. La RFC les a remplacés par des équivalents bien définis, et ce testeur s'en tient à la RFC.
- Qu'est-ce qu'un chemin normalisé ?
- L'emplacement canonique d'un seul nœud, écrit dans la forme à crochets et guillemets que définit la RFC, par ex. $['store']['book'][0]['author']. Une requête peut correspondre à de nombreux nœuds ; chaque correspondance a exactement un chemin normalisé, c'est pourquoi le testeur l'affiche à côté de chaque valeur.
- Comment un filtre traite-t-il une valeur absente ?
- Comme rien, avec des règles que la RFC fixe : rien égale rien, rien n'égale aucune valeur réelle, et toute comparaison d'ordre (<, >) impliquant rien est fausse. Ainsi @.price < 10 ignore simplement un élément sans price au lieu de lever une erreur.
- match() et search() utilisent-elles les expressions régulières de JavaScript ?
- Non. Elles utilisent I-Regexp (RFC 9485), un sous-ensemble portable. La principale différence pratique est le point, qui correspond à tout sauf le retour chariot et le saut de ligne — y compris U+2028 et U+2029, qu'un point de JavaScript exclut. Ce testeur compile I-Regexp fidèlement pour que les résultats correspondent à une implémentation conforme.
- Quelle est la différence entre match et search ?
- L'ancrage. match() exige que toute la chaîne corresponde au motif, comme si elle était entourée d'ancres ; search() réussit si le motif se trouve n'importe où dans la chaîne. Tout le reste est identique.
- Mon JSON est-il envoyé à un serveur ?
- Non. Le document est analysé et la requête évaluée entièrement dans votre navigateur, et rien de ce que vous collez n'est téléversé ni journalisé. C'est sûr à tester sur une vraie réponse d'API ou un fichier de configuration.