Cours de Python / Lire, écrire et valider des données JSON

JSON, pour JavaScript Object Notation, est un format texte d’échange de données structurées. Dans le module Python json, load et dump travaillent avec un fichier, tandis que loads et dumps travaillent avec du texte en mémoire ; JSON ne conserve pas tous les types d’objets Python à l’identique.

Relier les valeurs JSON aux types Python

Valeur JSON Valeur Python habituelle
object dict
array list
string str
Nombre entier ou décimal int ou float selon sa représentation
true, false, null True, False, None

Les chaînes et les clés d’un objet JSON utilisent des guillemets doubles. Les apostrophes d’un littéral Python, les commentaires et les virgules finales ne sont pas des ajouts arbitrairement permis en JSON. Une chaîne contenant un dictionnaire Python affiché avec print n’est donc pas nécessairement un document JSON valide.

Un tuple sérialisé comme un tableau JSON revient normalement comme une liste à la relecture. Les dates et les objets Decimal ne sont pas sérialisés par défaut : il faut définir une représentation explicite, par exemple un format de date ou une convention pour les décimaux. La documentation officielle de json décrit les conversions et leurs paramètres.

Enregistrer une dépense Unicode et la relire

Python 3 et sa bibliothèque standard suffisent. Utilisez un dossier d’exercice : le programme remplace expenses-demo.json. Le nom café sert à rendre visible un caractère non ASCII ; KRW reste l’unité de l’exemple, le won sud-coréen.

Enregistrez le bloc complet dans json_demo.py, en UTF-8. Dans un terminal ouvert dans ce dossier avec l’environnement virtuel du projet activé, lancez python json_demo.py. Sur macOS ou Ubuntu hors environnement virtuel, utilisez python3 json_demo.py si votre interpréteur porte ce nom. Évitez le nom de script json.py pour ne pas masquer le module standard.

import json
from pathlib import Path

data = {"currency": "KRW", "expenses": [{"name": "café", "amount": 8000}]}
path = Path("expenses-demo.json")
path.write_text(json.dumps(data, ensure_ascii=False, indent=2, allow_nan=False), encoding="utf-8")
loaded = json.loads(path.read_text(encoding="utf-8"))
amount = loaded["expenses"][0]["amount"]
if type(amount) is not int or amount < 0:
    raise ValueError("invalid amount")
print(loaded["expenses"][0]["name"])
print(amount)

Le résultat attendu est :

café
8000

dumps transforme data en texte JSON ; write_text l’enregistre ensuite en UTF-8. read_text relit ce texte et loads reconstruit les données Python. Le contrôle vérifie que le montant est exactement un entier natif non négatif avant d’afficher le nom et 8000. Ouvrez aussi le fichier généré pour vérifier les clés, le tableau expenses et le nom café.

Distinguer analyse syntaxique et validation métier

Un document JSON peut être syntaxiquement correct mais ne pas avoir les clés attendues, contenir un tableau vide ou fournir un montant du mauvais type. L’exemple suppose une structure connue et vérifie seulement son montant ; un importateur général doit aussi contrôler le type de l’objet racine, le tableau expenses, ses éléments et leurs champs avant de les indexer.

type(amount) is int exclut volontairement un booléen : True est une instance d’int en Python, mais ce n’est pas un montant accepté ici. Un test avec isinstance peut convenir si les sous-classes doivent être autorisées ; choisissez selon le contrat. Le test du montant négatif représente ensuite une règle distincte du type.

Fonction Entrée et résultat
load(file) Lit un document depuis un objet fichier et renvoie des données Python
loads(text) Analyse le texte JSON fourni
dump(data, file) Écrit le document dans un objet fichier
dumps(data) Renvoie une chaîne de texte JSON

allow_nan=False rejette NaN et les infinis lors de la sérialisation. Il ne configure pas le décodeur utilisé ensuite : par défaut, Python accepte certains nombres non standard à la lecture. Pour une entrée strictement conforme, définissez séparément une politique pour ces constantes, par exemple via parse_constant, et pour les clés répétées, qui sont remplacées par la dernière valeur avec le comportement habituel. Le contrôle de sortie ne remplace pas la validation d’un fichier extérieur.

Vérifier les caractères, les erreurs et les modifications

ensure_ascii=False conserve les caractères non ASCII sous une forme lisible lorsque leur échappement n’est pas obligatoire. Ce paramètre ne choisit pas l’encodage du fichier : UTF-8 est indiqué séparément dans write_text et read_text. indent=2 ajoute une présentation indentée sans changer la structure des données.

  1. Changez uniquement 8000 dans data en -1. La sérialisation réussit, mais la validation du montant lève ValueError: invalid amount, qui signifie « montant invalide ». Restaurez ensuite 8000.
  2. Dans le code Python, remplacez le montant par True avec une majuscule. Le JSON écrit contient alors true, mais la validation rejette le booléen à la relecture. Restaurez l’entier. La forme minuscule true appartient au texte JSON, pas à la syntaxe d’un littéral booléen Python.
  3. Changez seulement ensure_ascii en True. Le fichier représente le é par un échappement Unicode, mais la relecture affiche toujours café et 8000. Rétablissez False et vérifiez le fichier lisible.

Un texte mal formé provoque une JSONDecodeError : corrigez la syntaxe au lieu de remplacer json par eval. Une édition manuelle du fichier d’exercice serait écrasée au prochain lancement complet, puisque le programme commence par le réécrire. Pour éprouver une entrée extérieure, séparez la lecture de la génération du fichier et validez également sa structure.

Une donnée rejetée par la validation peut déjà avoir été écrite par ce programme pédagogique. Si seules des données approuvées doivent être enregistrées dans une application réelle, faites la validation avant l’écriture et définissez une stratégie de remplacement. Les différences entre contenu, encodage et protection de l’original suivent les règles de l’écriture de fichiers texte.