Leçon 9 / 15

Enregistrer et recharger des dépenses en JSON

25 min Python — modules, erreurs et données Gratuit

Objectif : Sérialiser une liste de dépenses en JSON puis la recharger en conservant sa structure.

Après l’import d’un relevé, les dépenses existent en mémoire, mais elles disparaissent lorsque le programme se termine si vous ne les enregistrez pas. Un fichier JSON permet de conserver cette liste et de la recharger plus tard sans perdre ses champs ni le type numérique des montants.

Vous allez compléter le module `depenses/fichiers.py`, qui contient déjà la lecture et la validation du CSV. Les fonctions JSON restent dans ce module : elles prennent une liste de dictionnaires et un chemin de fichier, et n’interviennent pas dans les règles de calcul des dépenses.

Sérialiser les dépenses dans un fichier

La sérialisation transforme des objets Python en une représentation enregistrable. Le module standard `json` sait convertir une liste de dictionnaires composée de chaînes, de nombres, de booléens, de valeurs nulles, de listes et de dictionnaires imbriqués. Pour les dépenses du projet, la structure reste simple : chaque élément possède `date`, `libelle` et `montant`.

Ajoutez les deux fonctions suivantes à `depenses/fichiers.py`. Le fichier complet ci-dessous reprend aussi la lecture CSV validée dans les leçons précédentes : les en-têtes attendus sont contrôlés, chaque ligne est contextualisée, et une date ou un montant incorrect provoque une erreur explicite. L’écriture JSON utilise `encoding="utf-8"` et `ensure_ascii=False` pour conserver les caractères accentués lisibles.

Texte
import csv
import json
from datetime import date


def lire_releve_csv(chemin):
    depenses = []
    colonnes_requises = {"date", "libelle", "montant"}

    with open(chemin, "r", encoding="utf-8", newline="") as fichier:
        lecteur = csv.DictReader(fichier)
        colonnes = set(lecteur.fieldnames or [])
        if not colonnes_requises.issubset(colonnes):
            manquantes = sorted(colonnes_requises - colonnes)
            raise ValueError(
                f"Colonnes CSV manquantes : {', '.join(manquantes)}"
            )

        for numero_ligne, ligne in enumerate(lecteur, start=2):
            date_texte = (ligne.get("date") or "").strip()
            libelle = (ligne.get("libelle") or "").strip()
            montant_texte = (ligne.get("montant") or "").strip()

            if not date_texte or not libelle or not montant_texte:
                raise ValueError(
                    f"Ligne {numero_ligne} : date, libellé et montant sont requis"
                )

            try:
                date.fromisoformat(date_texte)
                montant = float(montant_texte)
            except ValueError as erreur:
                raise ValueError(
                    f"Ligne {numero_ligne} : date ou montant invalide"
                ) from erreur

            depenses.append({
                "date": date_texte,
                "libelle": libelle,
                "montant": montant,
            })

    return depenses


def enregistrer_depenses_json(chemin, depenses):
    with open(chemin, "w", encoding="utf-8") as fichier:
        json.dump(depenses, fichier, ensure_ascii=False, indent=2)


def lire_depenses_json(chemin):
    with open(chemin, "r", encoding="utf-8") as fichier:
        return json.load(fichier)

`json.dump` écrit directement dans le fichier ouvert ; `indent=2` rend le contenu facile à relire, tandis que `ensure_ascii=False` évite de remplacer les accents par des séquences échappées. La fonction de lecture utilise `json.load` et reconstruit une liste Python de dictionnaires. Si le fichier est absent, illisible ou contient un JSON mal formé, l’exception correspondante remonte à l’appelant : ces erreurs ne sont pas ignorées.

Vérifier l’aller-retour

Pour vérifier les fonctions sans modifier `depenses/main.py`, lancez Python depuis la racine du projet, puis saisissez ces instructions dans l’interpréteur. L’exemple enregistre deux dépenses, recharge le fichier et compare les données obtenues à la liste initiale. Le chemin `depenses.json` désigne ici un fichier créé à la racine du projet.

Texte
from depenses.fichiers import (
    enregistrer_depenses_json,
    lire_depenses_json,
)

depenses = [
    {"date": "2025-01-15", "libelle": "Courses", "montant": 42.8},
    {"date": "2025-01-16", "libelle": "Transport", "montant": 18.5},
]

enregistrer_depenses_json("depenses.json", depenses)
depenses_rechargees = lire_depenses_json("depenses.json")
print(depenses_rechargees)
print(depenses_rechargees == depenses)
Résultat attendu
[{'date': '2025-01-15', 'libelle': 'Courses', 'montant': 42.8}, {'date': '2025-01-16', 'libelle': 'Transport', 'montant': 18.5}]
True

La comparaison confirme que les deux éléments, leurs clés et leurs valeurs ont été conservés ; `montant` est toujours un nombre et non une chaîne de caractères. Le fichier obtenu contient notamment des objets JSON séparés par des virgules, avec des chaînes encadrées de guillemets et des montants numériques. Vous pouvez l’ouvrir dans un éditeur de texte pour vérifier sa forme lisible.

À retenir

  • `json.dump` enregistre une structure Python ; `json.load` la reconstruit depuis un fichier.
  • Ouvrez les fichiers JSON en UTF-8 et gardez `ensure_ascii=False` pour lire les accents directement.
  • Enregistrez les montants comme des nombres afin qu’ils restent numériques après rechargement.
  • Ne masquez pas les erreurs d’ouverture ou de décodage : laissez-les remonter au niveau qui peut les traiter.

Dans la prochaine leçon, vous construirez une requête HTTP pour demander un taux de change à une API.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.