Tutoriel

JSON en Python : lecture et écriture de fichiers, CSV, pathlib et encodage UTF-8

Mis à jour: · Skillaria.Top

Pour travailler avec JSON en Python, le module intégré json suffit : json.load lit les données d'un fichier ouvert, json.dump les écrit dans un fichier, et json.loads et json.dumps font de même avec des chaînes. Le fichier s'ouvre avec with open(chemin, encoding="utf-8"), les chemins se construisent avec pathlib.Path, et les tableaux se lisent avec le module csv.

Travailler avec JSON en Python ne demande aucune bibliothèque externe : le module json fait partie de la bibliothèque standard et transforme dictionnaires et listes en texte, et inversement. À côté, on a presque toujours besoin de trois autres outils : la fonction open avec la construction with, le module pathlib pour les chemins, et le module csv pour les tableaux. Voici l'essentiel, dans l'ordre, avec du code qui fonctionne et les pièges les plus courants.

La fonction open en Python : modes et encodage

Un fichier texte s'ouvre avec la fonction intégrée open(chemin, mode, encoding=...). Elle renvoie un objet fichier, doté des méthodes read, readline, write, et que l'on peut parcourir ligne par ligne dans une boucle.

ModeCe qu'il fait
"r"Lecture (par défaut). Fichier absent : FileNotFoundError
"w"Écriture. Le fichier est créé ou vidé dès l'ouverture
"a"Ajout à la fin, le contenu existant n'est pas modifié
"x"Création d'un nouveau fichier ; s'il existe déjà : FileExistsError
"b"Complément du mode : travail en octets ("rb", "wb")

Pourquoi utiliser with

Un fichier doit être fermé : sinon les données risquent de ne pas être écrites sur le disque, et le système garde le descripteur occupé. La construction with ferme le fichier automatiquement, même si une erreur survient dans le bloc.

with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("Première ligne\n")
    f.write("Deuxième ligne\n")

with open("notes.txt", encoding="utf-8") as f:
    for ligne in f:
        print(ligne.rstrip("\n"))

Parcourir le fichier dans une boucle lit une ligne à la fois sans charger tout le contenu en mémoire : c'est ainsi qu'on traite des journaux de plusieurs gigaoctets. La méthode f.read() renvoie tout le contenu d'un seul bloc, f.readlines() renvoie une liste de lignes.

Ajouter une ligne sans effacer l'existant

L'erreur la plus courante chez les débutants est d'ouvrir le fichier en mode "w" pour « ajouter » une ligne. Le mode "w" vide le fichier au moment de l'ouverture, avant même le premier write. Pour ajouter en fin de fichier, utilisez "a", et pour être certain de ne jamais écraser un fichier existant, utilisez "x".

with open("journal.txt", "a", encoding="utf-8") as f:
    f.write("nouvelle entrée\n")

Notez que write n'ajoute pas de saut de ligne tout seul : le "\n" s'écrit explicitement. On peut aussi utiliser print(..., file=f), qui ajoute le retour à la ligne à votre place.

Encodage UTF-8 : d'où viennent les caractères illisibles

Avant Python 3.15, la fonction open() sans paramètre encoding utilisait l'encodage du système. Sous Linux et macOS, c'est UTF-8 ; sous Windows avec une locale française, c'est cp1252 ou cp1251 selon la configuration. Un script qui fonctionnait sur le serveur affichait alors des caractères brouillés sur l'ordinateur d'un collègue, ou plantait avec UnicodeDecodeError.

Dans Python 3.15, selon la PEP 686, le mode UTF-8 est activé par défaut : open() sans encoding lit et écrit en UTF-8 sur tous les systèmes. On peut revenir à l'ancien comportement avec la variable d'environnement PYTHONUTF8=0 ou l'option -X utf8=0, et demander explicitement l'encodage du système avec encoding="locale".

import sys
print(sys.flags.utf8_mode)   # 1 dans Python 3.15

open("ancien.txt", encoding="locale")      # encodage du système
open("ancien_excel.csv", encoding="cp1251") # fichier enregistré en cp1251

Revers de la médaille : un vieux CSV issu d'Excel ou d'un script Windows, enregistré en cp1251, sera désormais lu comme de l'UTF-8 et provoquera UnicodeDecodeError. La solution consiste à le lire avec encoding="cp1251", puis à le réenregistrer en UTF-8. La bonne pratique ne change pas : indiquez encoding explicitement, et le code se comportera de la même façon sur toutes les versions.

pathlib en Python : des chemins sous forme d'objets

Assembler des chemins par concaténation de chaînes ("data" + "/" + nom) est peu pratique et ne fonctionne pas de la même façon selon les systèmes. Le module pathlib fournit la classe Path : les chemins se combinent avec l'opérateur /, et l'objet offre des propriétés claires.

from pathlib import Path

data = Path("data")
chemin_csv = data / "depenses.csv"

print(chemin_csv.name)     # depenses.csv
print(chemin_csv.stem)     # depenses
print(chemin_csv.suffix)   # .csv
print(chemin_csv.parent)   # data
print(chemin_csv.exists())

data.mkdir(parents=True, exist_ok=True)
(data / "bonjour.txt").write_text("Привет", encoding="utf-8")
print((data / "bonjour.txt").read_text(encoding="utf-8"))

for p in data.glob("*.csv"):
    print(p)

Les méthodes read_text et write_text remplacent le couple open + with pour les petits fichiers. Un objet Path peut être passé à open, à json ou à csv, qui l'acceptent tous. Pour obtenir un chemin relatif au script lui-même, et non au dossier courant, écrivez Path(__file__).parent / "data".

Depuis Python 3.14, Path possède les méthodes copy() et move(), de sorte que shutil n'est plus nécessaire pour une simple copie. Dans la même version, la bibliothèque standard reçoit le module compression.zstd pour la compression Zstandard (PEP 784).

JSON en Python : load, dump, loads, dumps

JSON est un format texte d'échange de données : les API le renvoient, et on l'utilise pour stocker des configurations et des rapports. Le module json sait faire deux opérations : la sérialisation (objet Python vers texte JSON) et la désérialisation (texte vers objet). Il y a quatre fonctions, et la confusion entre elles est une question fréquente.

FonctionCe qu'elle fait
json.dumps(obj)Objet → chaîne JSON
json.loads(s)Chaîne JSON → objet
json.dump(obj, f)Objet → écriture dans un fichier ouvert
json.load(f)Lecture du JSON depuis un fichier ouvert

Pour retenir : la lettre s à la fin signifie string, c'est-à-dire le travail avec une chaîne ; sans elle, on travaille avec un fichier.

Correspondance des types

PythonJSON
dictobjet {}
list, tupletableau []
strchaîne
int, floatnombre
True / Falsetrue / false
Nonenull

Un tuple redevient une liste à la relecture, et le module ne connaît ni les ensembles, ni les dates, ni Decimal : il renvoie alors TypeError: Object of type ... is not JSON serializable.

Enregistrer du JSON dans un fichier

import json
from pathlib import Path

rapport = {
    "mois": "2026-10",
    "total": 15230.5,
    "categories": {"alimentation": 8200, "transport": 2100},
    "etiquettes": ["maison", "travail"],
}

chemin = Path("data/rapport.json")
chemin.parent.mkdir(parents=True, exist_ok=True)
with chemin.open("w", encoding="utf-8") as f:
    json.dump(rapport, f, ensure_ascii=False, indent=2)

Le paramètre ensure_ascii=False conserve les caractères accentués tels quels. Sans lui, « alimentation » s'écrirait avec des séquences d'échappement \u : c'est du JSON correct, mais illisible pour un humain. indent=2 produit une mise en forme aérée avec retraits.

Lire du JSON depuis un fichier

with open("data/rapport.json", encoding="utf-8") as f:
    donnees = json.load(f)

print(donnees["total"])                    # 15230.5
print(donnees["categories"]["alimentation"])  # 8200

Le résultat est un dictionnaire ou une liste ordinaire, qu'on manipule avec les outils habituels. Si le fichier n'est pas du JSON ou qu'il est tronqué, Python lève l'exception json.JSONDecodeError, qui indique la ligne et la position de l'erreur. Il faut l'intercepter lorsque les données viennent de l'extérieur.

Analyser une chaîne JSON et afficher joliment

import json

brut = '{"nom": "Anna", "competences": ["python", "sql"], "actif": true}'
utilisateur = json.loads(brut)
print(utilisateur["competences"][0])   # python

print(json.dumps(utilisateur, ensure_ascii=False, indent=2, sort_keys=True))

sort_keys=True trie les clés à l'affichage, pratique pour comparer des fichiers dans git. Pour mettre en forme rapidement un fichier depuis le terminal, il existe un utilitaire intégré : python -m json.tool data/rapport.json.

La réponse d'une API HTTP est elle aussi une chaîne JSON. La bibliothèque requests l'analyse avec la méthode response.json(), qui repose en interne sur le même json.loads.

Piège : les clés JSON sont toujours des chaînes

d = {1: "janvier", 2: "février"}
retour = json.loads(json.dumps(d))
print(retour == d)   # False
print(retour)        # {'1': 'janvier', '2': 'février'}
# retour[1] -> KeyError, il faut écrire retour["1"]

corrige = {int(k): v for k, v in retour.items()}

En JSON, les clés d'un objet ne peuvent être que des chaînes : les clés numériques deviennent donc des chaînes après un aller-retour de sérialisation. Il faut les reconvertir à la lecture.

Types personnalisés : dates et Decimal

import json
from datetime import date

def par_defaut(obj):
    if isinstance(obj, date):
        return obj.isoformat()
    raise TypeError(f"impossible de sérialiser {type(obj).__name__}")

print(json.dumps({"jour": date(2026, 10, 1)}, default=par_defaut))
# {"jour": "2026-10-01"}

La fonction passée au paramètre default est appelée pour chaque objet inconnu. Pour des cas plus complexes, on peut hériter de json.JSONEncoder et redéfinir sa méthode default.

Ajouter un élément à un tableau JSON

On ne peut pas compléter un JSON en fin de fichier comme un journal texte : il faut lire le fichier, le modifier, puis le réécrire entièrement.

chemin = Path("data/elements.json")
elements = json.loads(chemin.read_text(encoding="utf-8")) if chemin.exists() else []
elements.append({"id": len(elements) + 1, "titre": "nouveau"})
chemin.write_text(json.dumps(elements, ensure_ascii=False, indent=2), encoding="utf-8")

Si les enregistrements sont nombreux et s'ajoutent en permanence, le format JSON Lines est plus commode : un objet JSON par ligne, et le fichier s'ouvre en mode "a".

CSV en Python : le module csv

CSV est un tableau stocké dans un fichier texte : les lignes sont des enregistrements, et les valeurs sont séparées par des virgules. Le module csv gère correctement les guillemets, les virgules à l'intérieur des valeurs et les retours à la ligne, ce que ne fait pas un simple split(",").

import csv

with open("data/depenses.csv", newline="", encoding="utf-8") as f:
    lecteur = csv.DictReader(f)
    total = 0.0
    for ligne in lecteur:
        total += float(ligne["montant"])
        print(ligne["date"], ligne["categorie"], ligne["montant"])
print("Total :", total)

csv.DictReader transforme chaque ligne en dictionnaire, en utilisant l'en-tête du fichier. Toutes les valeurs sont des chaînes : les nombres et les dates doivent être convertis (float, int, date.fromisoformat). Un fichier séparé par des points-virgules (le format qu'enregistre Excel dans les réglages régionaux français) se lit avec le paramètre delimiter=";".

lignes = [
    {"date": "2026-10-01", "categorie": "alimentation", "montant": 450},
    {"date": "2026-10-02", "categorie": "transport", "montant": 60},
]
with open("data/sortie.csv", "w", newline="", encoding="utf-8") as f:
    ecrivain = csv.DictWriter(f, fieldnames=["date", "categorie", "montant"])
    ecrivain.writeheader()
    ecrivain.writerows(lignes)

À l'écriture, newline="" est indispensable : sans lui, des lignes vides apparaissent entre les enregistrements sous Windows. Si le fichier doit être ouvert dans Excel, utilisez encoding="utf-8-sig" : la marque BOM aide Excel à reconnaître l'UTF-8.

Du CSV au JSON : exemple complet

import csv
import json
from collections import defaultdict
from pathlib import Path

source = Path("data/depenses.csv")
cible = Path("data/rapport.json")

par_categorie = defaultdict(float)
with source.open(newline="", encoding="utf-8") as f:
    for ligne in csv.DictReader(f):
        par_categorie[ligne["categorie"]] += float(ligne["montant"])

rapport = {"total": sum(par_categorie.values()), "categories": dict(par_categorie)}
cible.write_text(json.dumps(rapport, ensure_ascii=False, indent=2), encoding="utf-8")
print(cible.read_text(encoding="utf-8"))

C'est le squelette de tout script de génération de rapports : entrée en CSV, agrégation dans un dictionnaire, sortie en JSON.

Erreurs typiques

  • FileNotFoundError — le chemin est relatif et compté depuis le dossier de lancement, et non depuis le script. Utilisez Path(__file__).parent.
  • Le fichier a été vidé — il a été ouvert en "w" au lieu de "a".
  • UnicodeDecodeError — le fichier est dans un autre encodage ; indiquez-le explicitement (cp1251, utf-8-sig).
  • TypeError: Object of type set is not JSON serializable — convertissez en liste ou passez default.
  • KeyError après le chargement d'un JSON — les clés numériques sont devenues des chaînes.
  • Votre propre fichier s'appelle json.py ou csv.py — il masque le module standard, et import json importe alors votre fichier.

En bref

Pour le JSON en Python, la bibliothèque standard suffit : json.load et json.dump pour les fichiers, loads et dumps pour les chaînes, ensure_ascii=False et indent pour un affichage lisible. Ouvrez les fichiers avec with en précisant l'encodage, construisez les chemins avec pathlib, et lisez les tableaux avec csv.DictReader. Ce sujet est mis en pratique dans le cours sur Python : le suivi des dépenses lit les achats dans un CSV et enregistre le rapport en JSON.

Questions fréquentes

Quelle est la différence entre json.load et json.loads ?

json.load lit le JSON depuis un objet fichier ouvert, tandis que json.loads analyse une chaîne. De même, json.dump écrit dans un fichier, et json.dumps renvoie une chaîne.

Comment enregistrer des lettres accentuées en JSON sans séquences \u ?

Passez ensure_ascii=False à json.dump ou json.dumps, et ouvrez le fichier avec encoding="utf-8".

Faut-il installer une bibliothèque pour travailler avec JSON en Python ?

Non, le module json est intégré à la bibliothèque standard ; il suffit d'écrire import json.

Peut-on stocker une date ou un ensemble dans du JSON ?

Pas directement : il faut les convertir, par exemple une date en chaîne ISO avec isoformat, un ensemble en liste, ou passer une fonction au paramètre default.

Comment lire un JSON trop volumineux pour la mémoire ?

Le json.load classique lit le fichier en entier ; pour de gros volumes, stockez les données au format JSON Lines et analysez-les ligne par ligne avec json.loads.

Apprenez ce sujet en vidéo

Ce sujet est traité en détail avec de la pratique dans le cours « Python 3.15: от новичка до профи ».

Voir le cours