Je suis Charlie

Autres trucs

Accueil

Seulement les RFC

Seulement les fiches de lecture

Mon livre « Cyberstructure »

Ève

RFC 6901: JavaScript Object Notation (JSON) Pointer

Date de publication du RFC : Avril 2013
Auteur(s) du RFC : P. Bryan (Salesforce.com), K. Zyp (SitePen), M. Nottingham (Akamai)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF appsawg
Première rédaction de cet article le 3 avril 2013


Ce court RFC spécifie une syntaxe pour identifier un élément particulier dans un document JSON. Cela permettra de pointer vers l'intérieur d'un document, par exemple depuis un URI.

JSON (RFC 8259) est un format structuré de données très populaire sur le Web. Les documents JSON peuvent être de taille très variable et il serait intéressant d'avoir une syntaxe formelle pour désigner une partie bien spécifique d'un document JSON, comme XPointer le permet pour XML et le RFC 5147 pour le texte brut. Outre les URI cités plus haut, un des usages envisagés est la modification de documents JSON, via la norme JSON patch du RFC 6902, puisque la mise à jour nécessite de dire exactement où on va faire l'ajout ou la suppression de données.

La syntaxe est exposée en section 3 : un JSON pointer est une chaîne de caractères Unicode (JSON pointer travaille entièrement avec des caractères Unicode, pas avec des octets). Il contient plusieurs éléments séparés par des barres obliques. Ce caractère, ainsi que le tilde, sont donc spéciaux et, si on veut avoir leur valeur littérale, il faut un échappement, ~0 pour le ~ et ~1 pour le /. (Programmeurs, attention, en décodant, il faut d'abord remplacer tous les ~1 par des / avant de s'attaquer à la deuxième substitution, sinon ~01 sera mal décodé.)

Pour illustrer ces pointeurs, je vais utiliser pour tous les exemples les données des chemins de fer britanniques disponibles en JSON en http://api.traintimes.im/. Commençons par récupérer la liste des stations :

% wget http://api.traintimes.im/stations_all.json

On peut alors connaître les codes tiploc des gares, ici, on va utiliser WMOR pour Woodsmoor. Cherchons les trains qui passent à Woodsmoor aux alentours de midi :

% wget -O wmor.json http://api.traintimes.im/locations.json\?location=WMOR\&date=2013-02-14\&startTime=1200

Le fichier JSON (passé à jsonlint) ressemble à :

{
  ...
  "services" : [ { 
       "departure_time" : "1141",
         "destination" : { "arrival_time" : "1145",
            "crs" : "HAZ",
            "description" : "Hazel Grove",
            "tiploc" : "HAZL"
          }},
        "departure_time" : "1206",
          "destination" : { "arrival_time" : "1227",
            "crs" : "MAN",
            "description" : "Manchester Piccadilly",
            "tiploc" : "MNCRPIC"
          }} ...

Par exemple, avec ces données /services, /services/1 et /services/1/destination/description sont tous des pointeurs JSON valides.

Et comment est-ce que cela s'interprète ? La section 4 le décrit. On part de la racine du document JSON. Donc, le pointeur composé d'une chaîne vide identifie la totalité du document. Ensuite, si la racine est un objet JSON (avec des champs nommés), l'élement suivant identifie un des champs. Ainsi, /foo identifie le champ foo de l'objet situé à la racine (il doit être unique). Si par contre la racine est un tableau, l'élement suivant doit être un nombre, l'index dans le tableau (en partant de zéro donc /1 identifie le deuxième élément du tableau situé à la racine). Une seule valeur non-numérique est permise, - qui indique l'élément situé après la fin du tableau (pour le JSON patch du RFC 6902, cela sert aux ajouts à la fin du tableau, autrement, cela désigne un élément non-existant). Donc, attention, / n'identifie pas la racine du document mais un élément de nom vide situé à la racine.

Le pointeur JSON est ensuite représenté sous forme texte comme une chaîne de caractères JSON (section 5) ce qui oblige à échapper les caractères spéciaux des chaînes JSON comme la barre inverse ou comme le guillemet. Par exemple, il faudra écrire a\\b pour trouver le membre d'objet JSON nommé a\b.

Et dans un URI (section 6) ? Il faudra encoder le pointeur en UTF-8 et faire un échappement « pour cent » (RFC 3986, section 2.1) des caractères qui sont spéciaux dans un URI (comme le point d'interrogation, section 2.2 du RFC 3986). Ainsi, http://api.traintimes.im/locations.json?location=WMOR\&date=2013-02-14\&startTime=1200#/services/1/destination/arrival_time pointera vers la valeur 1227 (regardez l'identificateur de fragment après le croisillon : c'est un pointeur JSON). À noter que tous les types MIME n'accepteront pas forcément le pointeur JSON comme un moyen normal de désigner un fragment du document JSON et l'exemple ci-dessus est donc hypothétique.

Reste à traiter le cas des erreurs (section 7). Deux erreurs typiques sont un élément non numérique dans le pointeur alors que la valeur est un tableau, ou bien un nom de champ inexistant. Le RFC ne spécifie pas ce qui doit se produire dans un tel cas, cela doit être défini par l'application qui utilise les pointeurs (pour JSON patch, voir les sections 4.1 et 5 du RFC 6902).

Une liste des mises en œuvre connues des pointeurs JSON est disponible à l'IETF (en fait, ce sont les mises en œuvre de JSON Patch mais, comme les pointeurs sont un pré-requis, elle couvre aussi les mises en œuvre des pointeurs). Il existe aussi une implémentation « faite en 20 minutes » en node.js, js-6901. Testons l'implémentation Python :

% git clone https://github.com/stefankoegl/python-json-pointer.git
% cd python-json-pointer
% python setup.py build
% sudo python setup.py install

Et lançons l'interpréteur Python pour voir :


>>> import jsonpointer
>>> import json
>>> doc = json.load(open("wmor.json"))

>>> jsonpointer.resolve_pointer(doc,'/services/1/destination/description')
u'Manchester Piccadilly'

>>> jsonpointer.resolve_pointer(doc,'/services/0/destination/description')
u'Hazel Grove'

>>> jsonpointer.resolve_pointer(doc,'/services/2/destination')
{u'crs': u'BUX', u'arrival_time': u'1251', u'description': u'Buxton', u'tiploc': u'BUXTON'}

Tout marche donc bien et on récupère, soit des valeurs, soit des objets JSON. Et avec un pointeur qui ne pointe vers rien ?


>>> jsonpointer.resolve_pointer(doc,'/zzz')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/jsonpointer.py", line 105, in resolve_pointer
    return pointer.resolve(doc, default)
  File "/usr/local/lib/python2.7/dist-packages/jsonpointer.py", line 140, in resolve
    doc = self.walk(doc, part)
  File "/usr/local/lib/python2.7/dist-packages/jsonpointer.py", line 183, in walk
    raise JsonPointerException("member '%s' not found in %s" % (part, doc))
jsonpointer.JsonPointerException: member 'zzz' not found in { ...

On a, fort logiquement, une exception.


Téléchargez le RFC 6901

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)