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

URL: https://skillaria.top/fr/guides/python/lire-ecrire-json-python

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. 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.

## FAQ

**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.


---
Source: Skillaria.Top (https://skillaria.top)
