Tom Daluzeau
← Parcours Python · fichiers
Python · fichiers · leçon 2 / 4Ch. 2 · JSON13 minTerminée

JSON : load, dump et fichier en erreur

Passer d'un fichier JSON à des listes et dictionnaires Python et inversement, choisir entre load/dump et loads/dumps, et arrêter proprement le script avec sys.exit quand le fichier est inutilisable.

Mis à jour le

À recouper avec le cours. Cette leçon a été écrite à partir du plan du chapitre, avant relecture avec les supports. En cas de doute, le cours fait foi.

Pourquoi on voit ça

JSON est le format d’échange le plus courant en DevOps : inventaires, fichiers de configuration, réponses d’API. C’est du texte, mais structuré exactement comme les listes et dictionnaires que tu connais.

Le fil rouge lit son parc dans servers.json. Deux questions se posent : comment transformer ce texte en objets Python, et que faire quand le fichier est absent ou cassé ? Un outil d’administration ne doit pas répondre par un traceback de vingt lignes.

Dans toute la leçon, servers.json contient :

[
  {"name": "web-1", "env": "prod", "port": 80, "up": true, "owner": null},
  {"name": "db-1", "env": "dev", "port": 5432, "up": false, "owner": "zoe"}
]

JSON ↔ Python : les types

JSON Python
objet {"name": "web-1"} dict
tableau [80, 443] list
chaîne "web-1" str
nombre 80, 0.5 int, float
true, false True, False
null None

Trois différences d’écriture à repérer : en JSON, true, false et null sont en minuscules, et les chaînes sont toujours entre guillemets doubles.

load et dump : avec un fichier

json.load(f) lit un fichier ouvert et renvoie l’objet Python.

import json

with open("servers.json", encoding="utf-8") as f:
    servers = json.load(f)

print(type(servers))
print(servers[0]["name"], servers[0]["port"] + 1)
print(servers[1]["up"], servers[0]["owner"])
<class 'list'>
web-1 81
False None

Le résultat est une vraie liste de dictionnaires. port est un vrai int : on peut calculer avec.

json.dump(data, f) fait l’inverse : il écrit un objet Python dans un fichier ouvert en écriture.

with open("report.json", "w", encoding="utf-8") as f:
    json.dump({"name": "web-1", "port": 80}, f)

loads et dumps : avec une chaîne

Le s final veut dire string. Ces deux fonctions ne touchent à aucun fichier.

Fonction Entrée Sortie
json.load(f) fichier ouvert objet Python
json.loads(text) chaîne objet Python
json.dump(data, f) objet Python + fichier ouvert rien (écrit dans le fichier)
json.dumps(data) objet Python chaîne
texte JSON Python un fichier ouvert f une chaîne text (str) objets Python list, dict, str int, True, None json.load(f) json.dump(data, f) avec un s : une chaîne, aucun fichier json.loads(text) json.dumps(data)
load et loads lisent du JSON vers Python ; dump et dumps écrivent du Python en JSON. Le s veut dire « chaîne ».
s = json.loads('{"name": "web-1", "up": true, "owner": null}')
print(s)
print(json.dumps({"name": "web-1", "up": True, "owner": None}))
{'name': 'web-1', 'up': True, 'owner': None}
{"name": "web-1", "up": true, "owner": null}

La première ligne est l’affichage d’un dict Python. La deuxième est du texte JSON.

Le piège classique : json.load("servers.json"). load attend un fichier ouvert, pas un nom : tu obtiens AttributeError: 'str' object has no attribute 'read'.

indent et ensure_ascii=False

Par défaut, dumps et dump écrivent tout sur une ligne et remplacent les accents par des codes.

print(json.dumps({"env": "préprod"}))
print(json.dumps({"env": "préprod"}, ensure_ascii=False))
print(json.dumps({"name": "web-1", "port": 80}, indent=2))
{"env": "pr\u00e9prod"}
{"env": "préprod"}
{
  "name": "web-1",
  "port": 80
}
  • indent=2 : une valeur par ligne, décalée de 2 espaces. Pour un fichier que des humains vont relire.
  • ensure_ascii=False : les accents restent tels quels. Pense alors à encoding="utf-8" dans le open.

Les dates : isoformat()

JSON n’a pas de type date. Donner un objet datetime à dumps lève une erreur :

TypeError: Object of type datetime is not JSON serializable

La solution : transformer la date en chaîne avec isoformat().

from datetime import datetime

d = datetime(2026, 11, 14, 9, 30)
print(json.dumps({"name": "web-1", "checked": d.isoformat()}))
{"name": "web-1", "checked": "2026-11-14T09:30:00"}

Le format ISO (année-mois-jour, T, heure) est le même partout et se trie correctement.

Fichier en erreur : l’erreur fatale

Deux choses peuvent rendre servers.json inutilisable :

  • il n’existe pas : FileNotFoundError ;
  • son contenu n’est pas du JSON valide : json.JSONDecodeError.

Dans les deux cas, impossible de continuer : sans inventaire, il n’y a rien à vérifier. C’est une erreur fatale. On affiche un message court et on arrête le script en signalant l’échec.

open(...) json.load(f) JSON valide absent ou cassé suite du script except (A, B) as e message court fin normale $? vaut 0 sys.exit(1) $? vaut 1 le shell ou la CI lit ce code pour savoir si l'étape a réussi
Erreur fatale : un message court et un code de sortie différent de 0, pas un traceback.
import json
import sys

try:
    with open("servers.json", encoding="utf-8") as f:
        servers = json.load(f)
except (FileNotFoundError, json.JSONDecodeError) as e:
    print("Erreur fatale :", e)
    sys.exit(1)

print(len(servers), "serveurs")

Avec un fichier absent :

Erreur fatale : [Errno 2] No such file or directory: 'servers.json'

except (A, B)

Pour traiter plusieurs exceptions de la même façon, mets-les dans un tuple, entre parenthèses. Avec as e, e contient l’exception qui s’est produite, quelle qu’elle soit.

sys.exit et $?

sys.exit(n) arrête le script tout de suite et donne au shell le code de sortie n. La convention : 0 = tout va bien, autre chose = échec. Dans le shell, $? contient le code de la dernière commande.

python3 fleetcheck.py
echo $?
Erreur fatale : [Errno 2] No such file or directory: 'servers.json'
1

C’est ce code qu’un script shell ou une chaîne d’intégration continue regarde pour savoir si l’étape a réussi. Un script qui se termine normalement sort avec 0.

SystemExit

sys.exit n’a rien de magique : il lève l’exception SystemExit. Deux conséquences :

  • un bloc finally s’exécute quand même avant l’arrêt ;
  • SystemExit n’est pas un enfant de Exception. Un except Exception ne l’attrape donc pas, et le script s’arrête bien.
BaseException Exception ValueError, KeyError… attrapées par except Exception SystemExit sys.exit(n) passe à travers except Exception
SystemExit est la sœur de Exception, pas sa fille : except Exception ne l'arrête pas.
import sys

try:
    sys.exit(2)
except Exception:
    print("attrapé")
finally:
    print("fin")
print("suite")
fin

Le code de sortie est 2. Ni attrapé ni suite ne s’affichent.

À retenir

  • JSON → Python : objet = dict, tableau = list, true/false = True/False, null = None.
  • load / dump travaillent avec un fichier ouvert ; loads / dumps avec une chaîne.
  • indent=2 pour un JSON lisible, ensure_ascii=False pour garder les accents.
  • Pas de date en JSON : on passe par isoformat().
  • Erreur fatale : except (FileNotFoundError, json.JSONDecodeError) as e: puis message et sys.exit(1).
  • sys.exit(n) lève SystemExit ; le shell lit le code avec echo $? (0 = succès).

Entraînement

12 questions, dont 7 à compléter. Tu réponds, on passe à la suivante. La note arrive à la fin.

Cartes · 8

Réviser les cartes ici

Espace : retourner