JSON en Python : lecture et écriture de fichiers, CSV, pathlib et encodage UTF-8
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.
| Mode | Ce 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.
| Fonction | Ce 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
| Python | JSON |
|---|---|
dict | objet {} |
list, tuple | tableau [] |
str | chaîne |
int, float | nombre |
True / False | true / false |
None | null |
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. UtilisezPath(__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 passezdefault.KeyErroraprès le chargement d'un JSON — les clés numériques sont devenues des chaînes.- Votre propre fichier s'appelle
json.pyoucsv.py— il masque le module standard, etimport jsonimporte 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.
Ce sujet est traité en détail avec de la pratique dans le cours « Python 3.15: от новичка до профи ».
Voir le cours