Lecture et écriture de fichiers JSON¶
Aperçu¶
JSON (JavaScript Object Notation) est un format d’échange de données léger et lisible par l’homme. Le module intégré
json de Python facilite le travail avec les données JSON, permettant des conversions fluides entre chaînes JSON et
objets Python.
Pourquoi utiliser JSON ?¶
JSON est idéal pour :
- Les API web et l’échange de données
- Les fichiers de configuration
- Le stockage de données structurées
- Le partage de données multiplateformes
- Le stockage de données lisibles par l’homme
Correspondance entre les types JSON et Python¶
Mappage JSON vers Python¶
| Type JSON | Type Python |
|---|---|
null |
None |
true/false |
True/False |
number |
int ou float |
string |
str |
array |
list |
object |
dict |
Opérations JSON de base¶
Lecture de fichiers JSON¶
import json
# Lire un JSON depuis un fichier
with open('data.json', 'r', encoding='utf-8') as file:
data = json.load(file)
print(data)
print(type(data)) # Généralement dict ou list
Écriture de fichiers JSON¶
import json
# Données d'exemple
data = {
"name": "Alice",
"age": 30,
"city": "New York",
"hobbies": ["reading", "cycling", "cooking"]
}
# Écrire un JSON dans un fichier
with open('output.json', 'w', encoding='utf-8') as file:
json.dump(data, file, indent=4, ensure_ascii=False)
Travail avec des chaînes JSON¶
import json
# Convertir un objet Python en chaîne JSON
data = {"name": "Bob", "age": 25}
json_string = json.dumps(data, indent=2)
print(json_string)
# Convertir une chaîne JSON en objet Python
parsed_data = json.loads(json_string)
print(parsed_data)
print(type(parsed_data)) # <class 'dict'>
Travail avec les types Python standard¶
Exemple 1 : Dictionnaire simple¶
import json
# Créer et sauvegarder une configuration simple
config = {
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp",
"ssl": True
},
"api": {
"timeout": 30,
"retries": 3,
"endpoints": [
"/api/users",
"/api/products",
"/api/orders"
]
},
"features": {
"logging": True,
"debug": False,
"cache_size": 1000
}
}
# Sauvegarder la configuration
with open('config.json', 'w', encoding='utf-8') as file:
json.dump(config, file, indent=4)
# Charger et utiliser la configuration
with open('config.json', 'r', encoding='utf-8') as file:
loaded_config = json.load(file)
db_host = loaded_config['database']['host']
api_timeout = loaded_config['api']['timeout']
print(f"Hôte de base de données: {db_host}")
print(f"Timeout API: {api_timeout} secondes")
Exemple 2 : Liste de dictionnaires¶
import json
# Enregistrements d'étudiants
students = [
{
"id": 1,
"name": "Alice Johnson",
"email": "alice@email.com",
"grades": {
"math": 95,
"science": 87,
"english": 92
},
"active": True
},
{
"id": 2,
"name": "Bob Smith",
"email": "bob@email.com",
"grades": {
"math": 78,
"science": 91,
"english": 85
},
"active": True
},
{
"id": 3,
"name": "Charlie Brown",
"email": "charlie@email.com",
"grades": {
"math": 82,
"science": 79,
"english": 88
},
"active": False
}
]
# Sauvegarder les données des étudiants
with open('students.json', 'w', encoding='utf-8') as file:
json.dump(students, file, indent=4)
# Charger et traiter les données des étudiants
with open('students.json', 'r', encoding='utf-8') as file:
loaded_students = json.load(file)
# Trouver les étudiants actifs avec une note en math > 80
high_math_students = [
student for student in loaded_students
if student['active'] and student['grades']['math'] > 80
]
print("Étudiants performants en mathématiques:")
for student in high_math_students:
print(f"- {student['name']}: {student['grades']['math']}")
Travail avec des classes personnalisées¶
Approche de base : Conversion manuelle¶
import json
class Person:
def __init__(self, name, age, email, hobbies=None):
self.name = name
self.age = age
self.email = email
self.hobbies = hobbies or []
def to_dict(self):
"""Convertir l'objet Person en dictionnaire"""
return {
'name': self.name,
'age': self.age,
'email': self.email,
'hobbies': self.hobbies
}
@classmethod
def from_dict(cls, data):
"""Créer un objet Person depuis un dictionnaire"""
return cls(
name=data['name'],
age=data['age'],
email=data['email'],
hobbies=data.get('hobbies', [])
)
def __repr__(self):
return f"Person(name='{self.name}', age={self.age}, email='{self.email}')"
# Créer des objets Person
people = [
Person("Alice", 30, "alice@email.com", ["reading", "hiking"]),
Person("Bob", 25, "bob@email.com", ["gaming", "cooking"]),
Person("Charlie", 35, "charlie@email.com", ["photography"])
]
# Convertir en dictionnaires et sauvegarder
people_data = [person.to_dict() for person in people]
with open('people.json', 'w', encoding='utf-8') as file:
json.dump(people_data, file, indent=4)
# Charger et convertir en objets Person
with open('people.json', 'r', encoding='utf-8') as file:
loaded_data = json.load(file)
loaded_people = [Person.from_dict(data) for data in loaded_data]
print("Personnes chargées:")
for person in loaded_people:
print(person)
Approche avancée : Encodage/Decodage JSON personnalisé¶
import json
from datetime import datetime, date
class Person:
def __init__(self, name, age, email, birth_date=None, hobbies=None):
self.name = name
self.age = age
self.email = email
self.birth_date = birth_date
self.hobbies = hobbies or []
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
class PersonEncoder(json.JSONEncoder):
"""Encodage JSON personnalisé pour les objets Person"""
def default(self, obj):
if isinstance(obj, Person):
return {
'__type__': 'Person',
'name': obj.name,
'age': obj.age,
'email': obj.email,
'birth_date': obj.birth_date.isoformat() if obj.birth_date else None,
'hobbies': obj.hobbies
}
elif isinstance(obj, (datetime, date)):
return obj.isoformat()
# Laisser la classe de base gérer les autres types
return super().default(obj)
def person_decoder(dct):
"""Décodage JSON personnalisé pour les objets Person"""
if '__type__' in dct and dct['__type__'] == 'Person':
birth_date = None
if dct['birth_date']:
birth_date = datetime.fromisoformat(dct['birth_date']).date()
return Person(
name=dct['name'],
age=dct['age'],
email=dct['email'],
birth_date=birth_date,
hobbies=dct['hobbies']
)
return dct
# Créer des objets Person avec dates
people = [
Person("Alice", 30, "alice@email.com", date(1993, 5, 15), ["reading", "hiking"]),
Person("Bob", 25, "bob@email.com", date(1998, 8, 22), ["gaming", "cooking"]),
Person("Charlie", 35, "charlie@email.com", date(1988, 12, 3), ["photography"])
]
# Sauvegarder avec l'encodage personnalisé
with open('people_advanced.json', 'w', encoding='utf-8') as file:
json.dump(people, file, cls=PersonEncoder, indent=4)
# Charger avec le décodage personnalisé
with open('people_advanced.json', 'r', encoding='utf-8') as file:
loaded_people = json.load(file, object_hook=person_decoder)
print("Personnes chargées avec décodage personnalisé:")
for person in loaded_people:
print(f"{person} - Né le: {person.birth_date}")
Exemple complexe : Système de gestion de bibliothèque¶
import json
from datetime import datetime, date
from typing import List, Optional
class Book:
def __init__(self, isbn, title, author, publication_year, genre=None):
self.isbn = isbn
self.title = title
self.author = author
self.publication_year = publication_year
self.genre = genre or "Unknown"
def to_dict(self):
return {
'isbn': self.isbn,
'title': self.title,
'author': self.author,
'publication_year': self.publication_year,
'genre': self.genre
}
@classmethod
def from_dict(cls, data):
return cls(
isbn=data['isbn'],
title=data['title'],
author=data['author'],
publication_year=data['publication_year'],
genre=data.get('genre', 'Unknown')
)
def __repr__(self):
return f"Book('{self.title}' by {self.author})"
class Member:
def __init__(self, member_id, name, email, join_date=None):
self.member_id = member_id
self.name = name
self.email = email
self.join_date = join_date or date.today()
self.borrowed_books = []
def borrow_book(self, book, due_date):
loan = {
'book': book,
'borrowed_date': date.today(),
'due_date': due_date
}
self.borrowed_books.append(loan)
def to_dict(self):
return {
'member_id': self.member_id,
'name': self.name,
'email': self.email,
'join_date': self.join_date.isoformat(),
'borrowed_books': [
{
'book': loan['book'].to_dict(),
'borrowed_date': loan['borrowed_date'].isoformat(),
'due_date': loan['due_date'].isoformat()
}
for loan in self.borrowed_books
]
}
@classmethod
def from_dict(cls, data):
member = cls(
member_id=data['member_id'],
name=data['name'],
email=data['email'],
join_date=datetime.fromisoformat(data['join_date']).date()
)
# Reconstruire les livres empruntés
for loan_data in data['borrowed_books']:
book = Book.from_dict(loan_data['book'])
borrowed_date = datetime.fromisoformat(loan_data['borrowed_date']).date()
due_date = datetime.fromisoformat(loan_data['due_date']).date()
loan = {
'book': book,
'borrowed_date': borrowed_date,
'due_date': due_date
}
member.borrowed_books.append(loan)
return member
def __repr__(self):
return f"Member(ID: {self.member_id}, Name: '{self.name}')"
class Library:
def __init__(self, name):
self.name = name
self.books = []
self.members = []
def add_book(self, book):
self.books.append(book)
def add_member(self, member):
self.members.append(member)
def to_dict(self):
return {
'name': self.name,
'books': [book.to_dict() for book in self.books],
'members': [member.to_dict() for member in self.members]
}
@classmethod
def from_dict(cls, data):
library = cls(data['name'])
# Reconstruire les livres
library.books = [Book.from_dict(book_data) for book_data in data['books']]
# Reconstruire les membres
library.members = [Member.from_dict(member_data) for member_data in data['members']]
return library
def save_to_json(self, filename):
"""Sauvegarder les données de la bibliothèque dans un fichier JSON"""
with open(filename, 'w', encoding='utf-8') as file:
json.dump(self.to_dict(), file, indent=4, ensure_ascii=False)
@classmethod
def load_from_json(cls, filename):
"""Charger les données de la bibliothèque depuis un fichier JSON"""
with open(filename, 'r', encoding='utf-8') as file:
data = json.load(file)
return cls.from_dict(data)
def get_overdue_books(self):
"""Trouver tous les livres en retard"""
today = date.today()
overdue = []
for member in self.members:
for loan in member.borrowed_books:
if loan['due_date'] < today:
overdue.append({
'member': member,
'book': loan['book'],
'due_date': loan['due_date'],
'days_overdue': (today - loan['due_date']).days
})
return overdue
# Exemple d'utilisation
def create_sample_library():
"""Créer une bibliothèque d'exemple avec des livres et des membres"""
library = Library("City Central Library")
# Ajouter des livres
books = [
Book("978-0-547-92822-7", "The Hobbit", "J.R.R. Tolkien", 1937, "Fantasy"),
Book("978-0-06-112008-4", "To Kill a Mockingbird", "Harper Lee", 1960, "Classic"),
Book("978-0-7432-7356-5", "The Da Vinci Code", "Dan Brown", 2003, "Thriller"),
Book("978-0-14-303943-3", "1984", "George Orwell", 1949, "Dystopian")
]
for book in books:
library.add_book(book)
# Ajouter des membres
member1 = Member(1, "Alice Johnson", "alice@email.com", date(2023, 1, 15))
member2 = Member(2, "Bob Smith", "bob@email.com", date(2023, 3, 22))
# Simuler l'emprunt de livres
from datetime import timedelta
member1.borrow_book(books[0], date.today() + timedelta(days=14)) # The Hobbit
member1.borrow_book(books[1], date.today() - timedelta(days=5)) # Livre en retard
member2.borrow_book(books[2], date.today() + timedelta(days=7)) # The Da Vinci Code
library.add_member(member1)
library.add_member(member2)
return library
# Créer et sauvegarder la bibliothèque
library = create_sample_library()
library.save_to_json('library.json')
print(f"Bibliothèque '{library.name}' sauvegardée avec {len(library.books)} livres et {len(library.members)} membres")
# Charger la bibliothèque depuis JSON
loaded_library = Library.load_from_json('library.json')
print(f"Bibliothèque '{loaded_library.name}' chargée")
# Vérifier les livres en retard
overdue = loaded_library.get_overdue_books()
if overdue:
print("\nLivres en retard:")
for item in overdue:
print(f"- {item['member'].name} a '{item['book'].title}' en retard de {item['days_overdue']} jours")
else:
print("\nAucun livre en retard!")
# Afficher tous les membres et leurs livres
print(f"\nMembres de la bibliothèque:")
for member in loaded_library.members:
print(f"- {member.name} (rejoint le {member.join_date})")
if member.borrowed_books:
for loan in member.borrowed_books:
status = "EN RETARD" if loan['due_date'] < date.today() else "OK"
print(f" * '{loan['book'].title}' à rendre le {loan['due_date']} [{status}]")
else:
print(" * Aucun livre emprunté")
Gestion des erreurs et bonnes pratiques¶
Lecture JSON robuste¶
import json
def safe_load_json(filename, default=None):
"""Charger un JSON de manière sécurisée avec une gestion d'erreur complète"""
try:
with open(filename, 'r', encoding='utf-8') as file:
return json.load(file)
except FileNotFoundError:
print(f"Fichier {filename} introuvable")
return default
except PermissionError:
print(f"Permission refusée pour accéder à {filename}")
return default
except json.JSONDecodeError as e:
print(f"JSON invalide dans {filename}: {e}")
return default
except UnicodeDecodeError as e:
print(f"Erreur d'encodage lors de la lecture de {filename}: {e}")
return default
except Exception as e:
print(f"Erreur inattendue lors de la lecture de {filename}: {e}")
return default
# Utilisation
data = safe_load_json('config.json', default={})
if data:
print("Configuration chargée avec succès")
else:
print("Utilisation de la configuration par défaut")
Validation de la structure JSON¶
import json
def validate_person_data(data):
"""Valider que les données contiennent les champs requis pour Person"""
required_fields = ['name', 'age', 'email']
if not isinstance(data, dict):
raise ValueError("Les données de Person doivent être un dictionnaire")
for field in required_fields:
if field not in data:
raise ValueError(f"Champ requis manquant: {field}")
if not isinstance(data['name'], str) or not data['name'].strip():
raise ValueError("Le nom doit être une chaîne non vide")
if not isinstance(data['age'], int) or data['age'] < 0:
raise ValueError("L'âge doit être un entier non négatif")
if not isinstance(data['email'], str) or '@' not in data['email']:
raise ValueError("L'email doit être une adresse email valide")
return True
def load_people_safely(filename):
"""Charger les données des personnes avec validation"""
try:
with open(filename, 'r', encoding='utf-8') as file:
data = json.load(file)
if not isinstance(data, list):
raise ValueError("Une liste de personnes était attendue")
people = []
for i, person_data in enumerate(data):
try:
validate_person_data(person_data)
people.append(Person.from_dict(person_data))
except ValueError as e:
print(f"Données de personne invalides à l'index {i}: {e}")
continue
return people
except Exception as e:
print(f"Erreur lors du chargement des personnes: {e}")
return []
# Utilisation
people = load_people_safely('people.json')
print(f"{len(people)} personnes chargées avec succès")
Techniques JSON avancées¶
Affichage formaté de JSON¶
import json
def pretty_print_json(data):
"""Afficher un JSON de manière formatée avec une mise en forme personnalisée"""
print(json.dumps(data, indent=4, sort_keys=True, ensure_ascii=False))
# Options de mise en forme personnalisées
data = {"name": "José", "age": 30, "hobbies": ["música", "fútbol"]}
# Différentes options de mise en forme
print("Compact:")
print(json.dumps(data, separators=(',', ':')))
print("\nFormaté avec clés triées:")
print(json.dumps(data, indent=2, sort_keys=True))
print("\nAvec caractères Unicode:")
print(json.dumps(data, indent=2, ensure_ascii=False))
Travail avec de grands fichiers JSON¶
import json
def process_large_json_file(filename, process_item_func):
"""Traiter les grands fichiers JSON élément par élément (en supposant qu'il s'agit d'une liste)"""
try:
with open(filename, 'r', encoding='utf-8') as file:
data = json.load(file)
if isinstance(data, list):
for i, item in enumerate(data):
try:
process_item_func(item, i)
except Exception as e:
print(f"Erreur lors du traitement de l'élément {i}: {e}")
else:
process_item_func(data, 0)
except Exception as e:
print(f"Erreur lors du traitement du fichier: {e}")
def process_person(person_data, index):
"""Fonction de traitement d'exemple"""
print(f"Traitement de la personne {index + 1}: {person_data.get('name', 'Inconnu')}")
# Utilisation
process_large_json_file('large_people.json', process_person)
Points clés à retenir¶
- Spécifiez toujours l’encodage lors de l’ouverture des fichiers (utilisez UTF-8)
- Gérez les erreurs JSON avec grâce à l’aide de blocs try-except
- Validez la structure des données avant le traitement
- Utilisez
ensure_ascii=Falsepour préserver les caractères Unicode - Considérez l’utilisation de la mémoire avec les grands fichiers JSON
- Créez des méthodes de conversion (
to_dict,from_dict) pour les classes personnalisées - Utilisez des encodages/décodages personnalisés pour les objets complexes
- Gérez explicitement les dates et objets datetime (JSON n’a pas de types de date natifs)
Erreurs courantes à éviter¶
- Ne pas gérer les exceptions
JSONDecodeError - Oublier que JSON ne supporte pas directement les ensembles, tuples ou objets datetime Python
- Ne pas valider la structure JSON avant le traitement
- Utiliser
eval()au lieu dejson.loads()(risque de sécurité) - Ne pas spécifier l’encodage lors de la lecture des fichiers
- Supposer la structure du fichier JSON sans validation
- Ne pas gérer correctement les valeurs None/null
- Oublier d’utiliser
ensure_ascii=Falsepour les caractères internationaux
Ce guide fournit une base complète pour travailler avec JSON en Python, des structures de données simples aux classes personnalisées complexes avec une gestion et validation des erreurs appropriées.