Date de publication du RFC : Février 2024
Auteur(s) du RFC : S. Gössner (Fachhochschule Dortmund), G. Normington, C. Bormann (Universität Bremen TZI)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF jsonpath
Première rédaction de cet article le 14 février 2025
Le langage JSONPath sert à désigner une partie d'un document JSON. Il est donc utile pour, par exemple, extraire des valeurs qui nous intéressent.
Il existe de nombreux langages pour cela (une liste partielle figure à la fin de l'article), pas forcément normalisés mais mis en œuvre dans tel ou tel logiciel (dont, évidemment, jq). Ce RFC est, je crois, la première normalisation formelle d'un tel langage. Voici tout de suite un exemple, utilisant le logiciel jpq, sur la la base des arbres parisiens :
% jpq '$[*].libellefrancais' arbres.json [ "Marronnier", "Aubépine", "Cerisier à fleurs", …
Cela se lit « affiche les noms d'espèce
[libellefrancais
, dans le fichier] de tous
[vous avez vu l'astérisque] les
arbres ». (Avec jq, on aurait écrit jq
'.[].libellefrancais' arbres.json
.) Voyons maintenant les
détails.
JSONPath travaille sur des
documents JSON, JSON étant normalisé dans le RFC 8259. Il n'est sans doute pas utile de
présenter JSON, qui est utilisé partout aujourd'hui. Notons
simplement que l'argument d'une requête JSONPath, écrit en JSON, est
ensuite modélisé comme un arbre, composé de
nœuds. Le JSON {"foo": 3, "bar": 0}
a ainsi une
racine de l'arbre sous laquelle se trouvent deux nœuds, avec comme
valeur 3 et 0 (les noms des membres JSON, foo
et bar
, ne sont pas sélectionnables par
JSONPath et donc pas considérés comme nœuds).
Vous êtes impatient·e de vous lancer ? On va utiliser le logiciel jpq cité plus haut. Pensez à installer un Rust très récent puis :
cargo install jpq
Ou bien, si vous voulez travailler à partir des sources :
git clone https://codeberg.org/KMK/jpq.git cd jpq cargo install --path .
Puis pensez à modifier votre chemin de recherche d'exécutables si ce n'est pas déjà fait (notez que l'excellente Julia Evans vient justement de faire un très bon article sur ce sujet). Testez :
% echo '["bof"]' | jpq '$[0]' [ "bof" ]
Les choses utiles à savoir pour écrire des expressins JSONPath :
[0]
est un segment sélectionnant
le premier élément d'un tableau, ici, le seul).La grammaire complète (en ABNF, RFC 5234) figure dans l'annexe A du RFC.
Allez, passons aux exemples, avec les arbres parisiens comme document JSON. Le quatrième arbre du document (on part de zéro) :
% jpq '$[3]' arbres.json [ { "idbase": 143057, "typeemplacement": "Arbre", "domanialite": "CIMETIERE", "arrondissement": "PARIS 20E ARRDT", "complementadresse": null, "numero": null, "adresse": "CIMETIERE DU PERE LACHAISE / DIV 41", "idemplacement": "D00000041050", "libellefrancais": "Erable", …
Tous les genres (dans la classification de Linné) :
% jpq '$[*].genre' arbres.json [ "Aesculus", "Crataegus", "Prunus", …
Les sélecteurs par nom peuvent s'écrire avec un point, comme ci-dessus, ou bien entre crochets avec des apostrophes (notez comme c'est moins pratique pour le shell Unix) :
% jpq "\$[*]['genre']" arbres.json [ "Aesculus", "Crataegus", "Prunus", …
La forme avec les crochets est la forme canonique pour une expression JSONPath. La notation avec un point n'est acceptée que pour les noms, pas pour les indices :
% echo '[1, 2, 3]' | jpq '$[1]' [ 2 ] % echo '[1, 2, 3]' | jpq '$.1' Error: × Invalid JSONPath: at position 2, in dot member name, must start with lowercase alpha or '_' ╭──── 1 │ $.1 · ▲ · ╰── in dot member name, must start with lowercase alpha or '_' ╰────
Tous les arbres qui font plus de 50 mètres de haut :
% jpq '$[?@.hauteurenm > 50].hauteurenm' < arbres.json [ 120, 69, 100, 67, 58, 74, 911 ]
L'arobase indique le nœud en cours d'évaluation. Et, oui, l'arbre de 911 mètres de haut est une erreur, chose courante dans les données ouvertes.
Comme vous avez vu, la requête JSONPath renvoie toujours une liste de nœuds, affichée par jpq dans la syntaxe des tableaux JSON.
Vous avez vu, dans l'exemple où on cherchait les plus grands arbres, que JSONPath permet de faire des comparaisons. Vous avez droit à tous les opérateurs booléens classiques. Ici, l'identificateur de tous les arbres situés dans un cimetière :
% jpq '$[?@.domanialite == "CIMETIERE"].idbase' < arbres.json [ 143057, 165296, 166709, …
JSONPath permet d'ajouter des fonctions, qui ne sont utilisables
que pour les comparaisons (je ne crois pas qu'on puisse mettre leur
résultat dans la valeur retournée par JSONPath). Plusieurs d'entre
elles sont définies par ce RFC, comme length()
,
mais pas forcément mises en œuvre dans tous les programmes. Et pour
compliquer les choses, des implémentations de JSONPath ajoutent
leurs propres fonctions non enregistrées et qui rendent l'expression
non portable.
Deux de ces fonctions,
search()
et match()
,
prennent en paramètre une expression
rationnelle, à la syntaxe du RFC 9485.
Les expressions JSONPath, quand on les envoie via l'Internet,
peuvent être marquées avec le type application/jsonpath
,
désormais enregistré
(section 3 du RFC). En plus, un
nouveau registre IANA est créé, pour stocker les fonctions
mentionnées au paragraphe précédent. Pour ajouter des fonctions à ce
registre, il faut passer par la procédure « Examen par un expert »
du RFC 8126.
La section 4, sur la sécurité, est très détaillée. JSONPath était souvent mis en œuvre en passant directement l'expression ou une partie d'entre elle au langage sous-jacent (typiquement JavaScript). Inutile de détailler les énormes problèmes de sécurité que cela pose si l'expression évaluée est, en tout ou en partie, fournie par un client externe (par exemple via un formulaire Web), permettant ainsi l'injection de code. Une validation de ces expressions avant de les passer étant irréaliste, le RFC insiste sur le fait qu'une mise en œuvre de JSONPath doit avoir son propre code et ne pas compter sur une évaluation par le langage de programmation sous-jacent.
Même ainsi, une expression JSONPath peut permettre une attaque par déni de service, si une expression mène à une consommation de ressources excessive. (Cf. la section 10 du RFC 8949 et la section 8 du RFC 9485.)
La section 1.2 de notre RFC décrit l'histoire un peu
compliquée de JSONPath. (Une des raisons pour lesquelles il
existe plusieurs langages de requête dans JSON est que JSONPath a
mis du temps à être terminé.) L'origine de JSONPath date de
2007, dans cet article de
Gössner (un des auteurs du RFC). L'interprétation de ce premier
JSONPath était souvent déléguée au langage sous-jacent (le
eval()
de JavaScript…)
avec les problèmes de sécurité qu'on imagine. Le JSONPath actuel se
veut portable, au sens où il ne dépend pas d'un langage
d'implémentation particulier, ni d'un moteur
d'expressions rationnelles
particulier. L'inconvénient est qu'il n'est pas forcément compatible
avec l'ancien JSONPath.
JSONPath a aussi été inspiré par le XPath de XML (cf. annexe B de notre RFC, qui détaille cette inspiration, et compare les deux langages). On peut aussi comparer JSONPath à JSON Pointer (RFC 6901), ce qui est fait dans l'annexe C.
Il existe bien sûr d'autres mises en œuvre de JSONPath que jpq. Mais attention : certaines sont très anciennes (comme Python JSONPath RW) et ne correspondent pas à ce qui est normalisé dans le RFC. Essayons Python JSONPath Next-Generation :
% python Python 3.13.2 (main, Feb 5 2025, 01:23:35) [GCC 14.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from jsonpath_ng.ext import filter, parse >>> import json >>> data = json.load(open("arbres.json")) >>> expr = parse("$[?@.hauteurenm > 50].hauteurenm") >>> result = expr.find(data) >>> for r in result: ... print(r.value) ... 120 69 100 67 58 74 911
D'autres langages de requête dans le JSON existent, chacun avec sa syntaxe :
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)