json
¶
Par Yoan Blanc 1
Introduction¶
JSON (JavaScript Object Notation) est un format simple, compact qui a, au fil des ans, remplacé XML comme format d’échange préféré. Standardisé au sein de l’Ecma [Bra14], il est devenu incontournable dans les systèmes actuels.
JSON comporte six types : chaîne de caractères, nombre, objet, tableau,
booléen (true
, false
) et null
.
{
"clé": "valeur",
"age": 20,
"pi": 314.59e-2,
"tableau": [1, 2, 3, ["x", "y"]],
"objet": {
"clé": "autre valeur",
"booléen": false
},
"rien": null
}
Il serait possible de lire directement cette structure de donnée en Python si
les valeurs booléennes et la valeur vide n’étaient pas écrites différemment :
True
, False
et None
.
>>> import ast
>>> ast.literal_eval('{"clé":"valeur","age":20, '
... '"tableau": [1, 2, 3, ["x", "y"]]}')
{'clé': 'valeur', 'age': 20, 'tableau': [1, 2, 3, ['x', 'y']]}
>>> ast.literal_eval('{"erreur": null}')
ValueError: malformed node or string: ...
Exemple¶
Le module json
est des plus simples à utiliser. Il est présenté par
le fameux Kenneith Reitz [Sch16] dans Hitchhiker’s Guide To Python.
L’API du module json
est similaire à celle utilisée par
marshal
et pickle
qui permettent de sérialiser des objets
Python.
load()
:charge un fichier JSON;
loads()
:charge une chaîne de caractères;
dump()
:écrit en JSON dans fichier;
dumps()
:écrit en JSON dans une chaîne de caractères.
>>> import json
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> json.dumps("€")
'"\\u20ac"'
>>> json.loads('[1, 2, "Hello"]')
[1, 2, 'Hello']
Un exemple travaillant avec un fichier externe. Un point très important est que JSON est toujours encodé en UTF-8.
{
"counter": 1
}
"""Exemple de lecture et d'écriture depuis et vers un fichier JSON."""
import json
entrée = "test.json"
sortie = "test.out.json"
with open(entrée, "r", encoding="utf-8") as fp:
données = json.load(fp)
données["counter"] += 1
with open(sortie, "w", encoding="utf-8") as fp:
json.dump(données, fp, sort_keys=True, indent=4)
Résultat :
{
"counter": 2
}
Validation¶
jsonschema
permet de valider un document JSON selon un modèle.
Consultez la documentation de JSON Schema pour en savoir plus.
{
"type": "object",
"properties": {
"counter": {"type": "number"}
}
}
"""Exemple de validation d'un fichier JSON avec JSON Schema."""
import json
from jsonschema import validate
filename = "test.json"
schema = "schema.json"
with open(schema, encoding="utf-8") as fp:
sch = json.load(fp)
with open(filename, encoding="utf-8") as fp:
data = json.load(fp)
validate(data, sch)
print(f"{filename} est valide selon {schema}.")
Formats binaires¶
Un document JSON est un fichier texte, qui consomme plus de place qu’une
représentation binaire. Python offre un format de sérialisation en binaire
nommé pickle
, comme il est possible d’utiliser des bibliothèques
externes telles que msgpack
, ou Apache Thrift.
Pickle¶
pickle
est le format utilisé par multiprocessing
pour échanger des données entre différents processus Python. C’est un format
qui est pratique mais non interopérable avec d’autres langages, voire même
d’autres versions de Python.
>>> import json
>>> from urllib.request import urlopen
>>> document = json.load(urlopen('https://www.reddit.com/.json'))
>>> doc = json.dumps(document, separators=',:').encode("utf-8")
>>> len(doc)
77266
>>> import pickle
>>> pack = pickle.dumps(document, protocole=pickle.HIGHEST_PROTOCOL)
>>> len(pack)
52227
Ce format n’est pas recommandé pour de multiples raisons. La documentation du
module informe que lire du pikle
revient à faire un eval
. Ben
Frederickson [Fre14] mentionne notamment la lenteur et la taille du
pickle
.
MessagePack¶
En alternative à pickle
, MessagePack permet de réduire efficacement
l’espace nécessaire au stockage et à l’échange de tels documents. En Python,
c’est le module msgpack
.
>>> import json
>>> from urllib.request import urlopen
>>> document = json.load(urlopen('https://www.reddit.com/.json'))
>>> doc = json.dumps(document, separators=',:').encode("utf-8")
>>> len(doc)
77244
>>> import msgpack
>>> pack = msgpack.packb(document)
>>> len(pack)
67017
Notez que cette petite différence n’est plus forcément intéressante si le
contenu est compressé à l’aide de gzip
.
>>> import gzip
>>> len(gzip.compress(doc))
14532
>>> len(gzip.compress(pack))
15002
Parfois, le mieux est l’ennemi du bien.
Streaming¶
Autre inconvénient majeur vis-à-vis du format XML est qu’il n’est pas aisé de
lire un document au fur et à mesure qu’il est reçu, en streaming. En XML, on
utilise une API nommée SAX. json
propose
un modèle demandant de charger l’entier d’un document en mémoire. Comme avec
DOM en XML. Ce problème se résout à l’aide de YAJL et du module ijson.
1 2 3 4 5 6 7 8 9 10 11 12 13 | """Lecture en streaming d'un gros document JSON."""
from pprint import pprint
from urllib.request import urlopen
import ijson
f = urlopen('https://www.reddit.com/.json')
# data.children représente le chemin dans le fichier
# dont les éléments nous intéressent.
for child in ijson.items(f, 'data.children'):
pprint(child)
|
Conclusion¶
JSON est un format de fichier à connaître, comprendre et savoir utiliser. Dans sa version basique, voire même dans sa version riche nommée JSON-LD utilisée par de nombreuses API. Si vous devez consommer des données JSON externe, il n’est que vivement recommandé d’ajouter un schéma afin d’offrir un message d’erreur adéquat en cas de non respect du document espéré. Et des solutions existent afin de contourner des problèmes de fichiers inutilement volumineux ou devant être chargés complètement en mémoire avoir de pouvoir être lus.
JSON c’est bon, mangez-en!
—Anonymous
- Bra14
Tim Bray. The javascript object notation (json) data interchange format. ECMA International®, 2014. URL: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf.
- Fre14
Ben Frederickson. Don’t pickle your data. 2014. URL: http://www.benfrederickson.com/dont-pickle-your-data/.
- Sch16
K.R.T. Schlusser. Hitchhiker’s Guide To Python. O’Reilly Media, Incorporated, 2016. ISBN 9781491933213. URL: http://docs.python-guide.org/en/latest/.