Python et JSON forment une paire naturelle. Que vous construisiez une API REST avec FastAPI ou Django, que vous traitiez des pipelines de données ou que vous lisiez simplement un fichier de configuration, vous travaillerez constamment avec du JSON. La bonne nouvelle : la bibliothèque standard de Python contient tout ce dont vous avez besoin dans le module json. Aucun pip install requis.

Les quatre fonctions que vous utilisez vraiment

Le module json vous offre quatre fonctions pour le travail quotidien :

  • json.loads(str) — analyse une chaîne JSON en objet Python
  • json.dumps(obj) — convertit un objet Python en chaîne JSON
  • json.load(file) — analyse du JSON directement depuis un objet fichier
  • json.dump(obj, file) — écrit un objet Python en JSON dans un fichier

Le s dans loads / dumps signifie string (chaîne). Ceux sans le s travaillent avec des objets fichier. Facile à retenir une fois que vous connaissez la règle.

json.loads() — Analyser une chaîne JSON

python
import json

json_string = '{"name": "Alice", "age": 30, "active": true, "score": 98.5}'

user = json.loads(json_string)

print(user["name"])    # Alice
print(user["age"])     # 30
print(user["active"])  # True
print(type(user))      # <class 'dict'>

Remarquez le mappage de types : JSON true devient Python True, JSON false devient Python False, JSON null devient Python None. Les objets JSON deviennent des dict Python, les tableaux JSON deviennent des list Python.

json.dumps() — Sérialiser en chaîne JSON

python
import json

user = {
    "name": "Bob",
    "age": 25,
    "roles": ["admin", "editor"],
    "active": True,
    "extra": None
}

# Compact (idéal pour la transmission réseau)
compact = json.dumps(user)
print(compact)
# {"name": "Bob", "age": 25, "roles": ["admin", "editor"], "active": true, "extra": null}

# Formaté (idéal pour les logs et la lecture humaine)
pretty = json.dumps(user, indent=2)
print(pretty)
# {
#   "name": "Bob",
#   "age": 25,
#   "roles": [
#     "admin",
#     "editor"
#   ],
#   "active": true,
#   "extra": null
# }

Remarquez le mappage de types inverse : Python True → JSON true, Python None → JSON null. Python gère cela automatiquement.

Lire du JSON depuis un fichier

C'est probablement le cas d'utilisation le plus courant — lire un fichier de configuration ou de données au démarrage :

python
import json

# Lire et analyser en une seule étape
with open("config.json", "r", encoding="utf-8") as f:
    config = json.load(f)

print(config["database"]["host"])  # localhost
print(config["database"]["port"])  # 5432

Spécifiez toujours encoding="utf-8" lors de l'ouverture de fichiers JSON. JSON est spécifié en UTF-8 par la RFC 8259, et l'omettre peut causer des problèmes sous Windows où l'encodage par défaut est parfois cp1252.

Écrire du JSON dans un fichier

python
import json

results = {
    "timestamp": "2024-01-15T09:30:00Z",
    "total": 1523,
    "processed": 1521,
    "failed": 2,
    "errors": [
        {"id": 42, "reason": "missing field"},
        {"id": 99, "reason": "invalid format"}
    ]
}

with open("results.json", "w", encoding="utf-8") as f:
    json.dump(results, f, indent=2)

print("Résultats sauvegardés dans results.json")

Gérer les erreurs correctement

json.loads() lève une exception json.JSONDecodeError (sous-classe de ValueError) lorsque l'entrée n'est pas du JSON valide. Gérez-la toujours lors de l'analyse de données que vous ne contrôlez pas :

python
import json

def safe_parse(json_str):
    try:
        return json.loads(json_str)
    except json.JSONDecodeError as e:
        print(f"JSON invalide à la ligne {e.lineno}, colonne {e.colno}: {e.msg}")
        return None

data = safe_parse('{"name": "Alice"}')   # fonctionne correctement
bad  = safe_parse('not json at all')     # affiche l'erreur, retourne None
also_bad = safe_parse('{"key": }')       # affiche l'erreur avec la position

JSONDecodeError vous donne la ligne et la colonne exactes où l'analyse a échoué, ce qui est utile lors du débogage de grands fichiers JSON.

Options utiles de dumps()

python
import json

data = {
    "z_key": 1,
    "a_key": 2,
    "price": 9.999999999
}

# Trier les clés par ordre alphabétique (idéal pour une sortie reproductible / diffs)
print(json.dumps(data, sort_keys=True, indent=2))
# {
#   "a_key": 2,
#   "price": 9.999999999,
#   "z_key": 1
# }

# S'assurer que les caractères non-ASCII sont préservés (par défaut : échappés en \uXXXX)
data2 = {"city": "Münich", "greeting": "こんにちは"}
print(json.dumps(data2, ensure_ascii=False))
# {"city": "Münich", "greeting": "こんにちは"}

# Avec ensure_ascii=True (par défaut) :
print(json.dumps(data2))
# {"city": "M\u00fcnich", "greeting": "\u3053\u3093\u306b\u3061\u306f"}

ensure_ascii=False est une option que j'ajoute toujours lors de l'écriture de fichiers JSON contenant du texte non-ASCII. La version échappée est techniquement du JSON valide, mais bien plus difficile à lire dans un éditeur de texte.

Sérialiser des objets personnalisés

Par défaut, json.dumps() ne peut pas sérialiser les instances de classes personnalisées ni les objets datetime. Vous avez deux options : créer une sous-classe de json.JSONEncoder, ou convertir en dict au préalable :

python
import json
from datetime import datetime, date

# Option 1 : classe d'encodeur personnalisée
class AppEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (datetime, date)):
            return obj.isoformat()
        return super().default(obj)

data = {"name": "Alice", "created_at": datetime(2024, 1, 15, 9, 30)}
print(json.dumps(data, cls=AppEncoder, indent=2))
# {
#   "name": "Alice",
#   "created_at": "2024-01-15T09:30:00"
# }

# Option 2 : paramètre default= (plus simple pour les conversions ponctuelles)
print(json.dumps(data, default=str, indent=2))  # convertit tout inconnu en str

Un pattern pratique : chargement de fichier de configuration

Voici un pattern du monde réel que j'utilise dans presque tous mes projets Python — un chargeur de configuration qui lit un fichier JSON avec des valeurs par défaut raisonnables :

python
import json
import os
from pathlib import Path

DEFAULTS = {
    "database": {"host": "localhost", "port": 5432},
    "debug": False,
    "log_level": "INFO"
}

def load_config(path="config.json"):
    config = DEFAULTS.copy()

    config_path = Path(path)
    if config_path.exists():
        with open(config_path, "r", encoding="utf-8") as f:
            try:
                user_config = json.load(f)
                # Fusion profonde : les paramètres utilisateur remplacent les valeurs par défaut
                for key, value in user_config.items():
                    if isinstance(value, dict) and key in config:
                        config[key].update(value)
                    else:
                        config[key] = value
            except json.JSONDecodeError as e:
                print(f"Avertissement : config.json est invalide ({e.msg}), utilisation des valeurs par défaut")

    return config

config = load_config()
print(config["database"]["host"])  # localhost (ou valeur remplacée)

Conclusion

Le module json de Python couvre tout ce dont vous avez besoin sans aucune dépendance. Les règles clés : utilisez loads()/dumps() pour les chaînes, load()/dump() pour les fichiers, gérez toujours JSONDecodeError lors de l'analyse de données externes, et ajoutez ensure_ascii=False quand vos données contiennent des caractères non-latins. Pour déboguer des données JSON, le Formateur JSON et le Validateur JSON peuvent vous faire gagner beaucoup de temps.