Python, lire et écrire des données JSON avec le package natif json

Introduction

Les fonctionnalités essentielles à aborder lorsqu’on souhaite apprendre et utiliser très rapidement Python :

Dans ce chapître, comment lire et écrire des données JSON dans un programme Python avec le package système json.

L’environnement Python

L’environnement python est le suivant, il est sourcé avec le fichier $HOME/.python-3.8:

$HOME/.python-3.8
#!/bin/bash
export PYHOME=/opt/python/python-3.8
export PATH=$PYHOME/bin:$PATH
export LD_LIBRARY_PATH=$PYHOME/lib:$LD_LIBRARY_PATH
export PYTHONPATH=/opt/python/packages

sqlpac@vpsfrsqlpac2$ . $HOME/.python-3.8
sqlpac@vpsfrsqlpac2$ which python3
sqlpac@vpsfrsqlpac2$ which pip3
/opt/python/python-3.8/bin/python3
/opt/python/python-3.8/bin/pip3

virtualenv est installé et un environnement virtuel isolé est mis en place pour le projet :

sqlpac@vpsfrsqlpac2$ cd /home/sqlpac
          
sqlpac@vpsfrsqlpac2$ virtualenv /home/sqlpac/google
Using base prefix '/opt/python/python-3.8'
New python executable in /home/sqlpac/google/bin/python3.8
Also creating executable in /home/sqlpac/google/bin/python
Installing setuptools, pip, wheel...
done.
sqlpac@vpsfrsqlpac2$ source /home/sqlpac/google/bin/activate
(google) sqlpac@vpsfrsqlpac2:/home/sqlpac$

Les données JSON de l’exemple

On connaît le format JSON renvoyé par les API Google Indexing lorsqu’on requête le statut d’indexation pour une URL donnée :

{
  "url": "https://www.sqlpac.com/referentiel/docs/mariadb-columnstore-1.2.3-installation-standalone-ubuntu.html",
  "latestUpdate": 
  { "url": "https://www.sqlpac.com/referentiel/docs/mariadb-columnstore-1.2.3-installation-standalone-ubuntu.html",    
    "type": "URL_UPDATED", 
    "notifyTime": "2020-04-10T17:43:21.198591915Z"
  }
}

La variable d’environnement $PRJ définit le répertoire de travail

(google) sqlpac@vpsfrsqlpac2:/home/sqlpac$ mkdir google/json
(google) sqlpac@vpsfrsqlpac2:/home/sqlpac$ export PRJ=/home/sqlpac/google/json

(google) sqlpac@vpsfrsqlpac2:/home/sqlpac$ cd $PRJ
(google) sqlpac@vpsfrsqlpac2:/home/sqlpac/google/json$

Voyons comment manipuler JSON dans un programme Python.

Le package système json

Le package système json est disponible en natif, il n’y a qu’à l’importer :

$PRJ/handling-json.py
import json

C’est fini !

Lecture des données JSON

La méthode loads : chargement depuis une variable string

Utiliser la méthode loads pour charger des données JSON depuis une variable string :

import json

response_json='{"a":1, "b":2}'

loaded_json = json.loads(response_json)

for key in loaded_json:
	print("key : %s, value: %s" % (key,loaded_json[key]))
(google) sqlpac@vpsfrsqlpac2:/home/sqlpac/google/json$ python3 handling-json.py
key : a, value: 1
key : b, value: 2

Dans la vraie vie, les données JSON ne sont pas définies avec un string sur une seule ligne , pour définir un string sur de multiples lignes :

import json

response_json = '''{
  "url": "https://www.sqlpac.com/referentiel/docs/mariadb-columnstore-1.2.3-installation-standalone-ubuntu.html",
  "latestUpdate":
  { "url": "https://www.sqlpac.com/referentiel/docs/mariadb-columnstore-1.2.3-installation-standalone-ubuntu.html",
    "type": "URL_UPDATED",
    "notifyTime": "2020-04-10T17:43:21.198591915Z"
  },
  "isactive" : true,
  "floatvalue" : 1.2399,
  "intvalue" : 1,
  "ostypes" : ["linux","macos","windows"]
}'''

loaded_json = json.loads(response_json)
for key in loaded_json:
  print("%s %s %s" % (key, type(loaded_json[key]), loaded_json[key]))

Des données supplémentaires sont ajoutées dans l’exemple JSON pour la démo : isactive, floatvalue, intvalue, ostypes.

Les types de données sont également affichés avec la fonction type(). Les types de données sont alors les suivants :

KeyTypeValeur
url<class 'str'>https://www.sqlpac.com/ref…
latestUpdate<class 'dict'>{'url': 'https://www.sqlpac.com/…', 'type': 'URL_UPDATED'…}
isactive<class 'bool'>True
floatvalue<class 'float'>1.2300
intvalue<class 'int'>1
ostypes<class 'list'>["linux","macos","windows"]

Lorsqu’on est habitués à Javascript, la translation pour le type de données est la suivante :

JavascriptPython
Objectdict
Arraylist
Stringstr
Number (int)int
Number (float)float
true | falseTrue | False
print(loaded_json["url"])
https://www.sqlpac.com/referentiel…

Alors naturellement, on essaie de manipuler les données obtenues avec la syntaxe Javascript "dot notation", mais cela ne fonctionne pas :

print(loaded_json.url)
Traceback (most recent call last):
  File "handling-json.py", line 23, in <module>
    print(loaded_json.url)
AttributeError: 'dict' object has no attribute 'url'

Pour utiliser la notation "dot", une classe doit être créée et associée :

import json

response_json = '''{
 "url": "https://www.sqlpac.com/referentiel/docs/mariadb-columnstore-1.2.3-installation-standalone-ubuntu.html",
 "latestUpdate":
 { "url": "https://www.sqlpac.com/referentiel/docs/mariadb-columnstore-1.2.3-installation-standalone-ubuntu.html",
   "type": "URL_UPDATED",
   "notifyTime": "2020-04-10T17:43:21.198591915Z"
 },
 "isactive" : true,
 "floatvalue" : 1.2399,
 "intvalue" : 1,
 "ostypes" : ["linux","macos","windows"]
}'''

class google():
	def __init__(self, data):
		self.__dict__ = json.loads(data)
	
google_answer = google(response_json)

print(google_answer.url)
print(google_answer.latestUpdate["type"])
https://www.sqlpac.com/referentiel…
URL_UPDATED

Comme attendu, google_answer.latestUpdate.type n’est pas disponible comme cela serait le cas avec Javascript, mais google_answer.latestUpdate["type"]. Python n’est pas Javascript, on doit parfois quitter ses habitudes de programmation.

La méthode load : chargement depuis un fichier

Quand les données JSON sont dans un fichier, la méthode load est utilisée :

import json

with open('json-data.json', 'r') as f:
    json_dict = json.load(f)

print(json_dict["url"])
https://www.sqlpac.com/referentiel…

Pas de différence par rapport à l’exemple précédent, pour utiliser la notation dot, créer une classe :

import json

class google():
	def __init__(self, filename):
		with open(filename, 'r') as f:
			self.__dict__ = json.load(f)
	

google_answer = google('json-data.json')
print(google_answer.url)
https://www.sqlpac.com/referentiel…

Gestion des données JSON mal formatées

Utiliser les blocs try / except pour gérer les exceptions rencontrées lors du chargement de données JSON mal formatées :

import json

with open('json-data.json') as f:
	try:
		data = json.load(f)
	except Exception as e:
		print("Exception raised | %s " % str(e))
		exit()
	
print(data["url"])
Exception raised | Expecting ',' delimiter: line 6 column 5 (char 306)

Doublons Clé/valeur

Qu’en est-il si une paire clé/valeur est définie plus d’une fois :

{
	"url": "1.html",
	"url": 1
}

Pas d’exception levée, la valeur et le type de données sont ceux de la dernière paire clé/valeur lue dans les données JSON :

import json
…
print("value : %s, data type : %s" % (data["url"], type(data["url"]) ))
value : 18, data type : <class 'int'>

Retourner et écrire des données JSON

Imaginons que l’on souhaite retourner la réponse factice ci-dessous :

{
    "url": "https://www.sqlpac.com/archives/2020",
    "ostypes": [ "linux", "macos","windows"],
    "isactive": true,
    "price": "12€",
    "details": {
        "returncode": "0",
        "reason": "none"
    }
}

La méthode dumps

La méthode dumps retourne un string JSON à partir du dictionnaire Python :

import json

response = {}

response["url"] = "https://www.sqlpac.com/archives/2020"
response["ostypes"] = ["linux","macos","windows"]
response["isactive"] = True
response["price"] = "12$"
response["details"] = { "returncode": 1, "reason":"none" }

str_response = json.dumps(response)
print(str_response)
{"url": "https://www.sqlpac.com/archives/2020", "ostypes": ["linux", "macos", "windows"], "isactive": true, "price": "12$", "details": {"returncode": 1, "reason": "none"}}

Les données sont bien transtypées dans le sens du retour :

PythonJavascript
dictObject
listArray
strString
intNumber (int)
floatNumber (float)
True | Falsetrue | false

"Human readable"

Les données sont retournées sur une ligne unique, utiliser l’option d’indentation indent pour obtenir un format plus lisible par un humain.

str_response = json.dumps(response, indent=4)
print(str_response)
{
    "url": "https://www.sqlpac.com/archives/2020",
    "ostypes": [
        "linux",
        "macos",
        "windows"
    ],
    "isactive": true,
    "price": "12$",
    "details": {
        "returncode": 1,
        "reason": "none"
    }
}

Unicode

Et si il y a un caractère Unicode, par exemple 12€ au lieu de 12$. Le résultat va ressembler à ceci :

…
        "price": "12\u20ac",
…

Par défaut, json.dumps garantit que le texte est encodé ASCII, si ce n’est pas le cas le texte est échappé. Appliquer l’option ensure_ascii à False pour s’assurer que les caractères unicode ne sont pas retouchés :

str_response = json.dumps(response, indent=4, ensure_ascii=False)
print(str_response)
…
        "price": "12€",
…

Trier les clés

L’ordre des clés n’est pas garanti ou prédéfini, pour forcer un ordre des clés, appliquer l’option sort_keys à True :

str_response = json.dumps(response, indent=4, ensure_ascii=False, sort_keys=True)
print(str_response)
{
    "details": {
        "reason": "none",
        "returncode": 1
    },
    "isactive": true,
    "ostypes": [
        "linux",
        "macos",
        "windows"
    ],
    "price": "12€",
    "url": "https://www.sqlpac.com/archives/2020"
}

La méthode dump

Utiliser la méthode dump pour écrire les données JSON dans un fichier, toutes les options décrites ci-dessus avec la méthode dumps sont disponibles avec la méthode dump :

with open('response.json', 'w') as f:
 json.dump(response,f,indent=4, ensure_ascii=False, sort_keys=False )
$PRJ/response.json
{
    "url": "https://www.sqlpac.com/archives/2020",
    "ostypes": [
        "linux",
        "macos",
        "windows"
    ],
    "isactive": true,
    "price": "12€",
    "details": {
        "returncode": 1,
        "reason": "none"
    }
}

Conclusion

Sérialiser ou désérialiser des données JSON est très simple mais on doit oublier nos habitudes Javascript lorsqu’on charge des données JSON avec des programmes Python (dot notation…).