Streaming JSON pour les gros fichiers : Traiter sans tout charger
Le parsing JSON standard charge l'intĂ©gralitĂ© du document en mĂ©moire, construit une structure de donnĂ©es complĂšte, puis vous donne accĂšs. Pour un fichier de 10 Mo, ça fonctionne bien. Pour un fichier de 10 Go, votre processus manque de mĂ©moire et plante. Les parseurs en streaming rĂ©solvent ce problĂšme en traitant le JSON de maniĂšre incrĂ©mentale â lisant et traitant les donnĂ©es au fur et Ă mesure de leur arrivĂ©e, sans jamais conserver l'intĂ©gralitĂ© du document en mĂ©moire.
Le problĂšme du parsing standard
import json
# Ceci charge le fichier ENTIER en mémoire
with open('huge.json') as f:
data = json.load(f) # 10 GB file = 10+ GB of RAM
# Le traitement se fait aprĂšs le chargement complet
for item in data['records']:
process(item)
Pour un fichier avec 1 million d'enregistrements à 10 Ko chacun, le parsing standard nécessite :
- Taille du fichier : ~10 Go
- Mémoire pour le parsing : ~10 Go (la chaßne brute)
- Mémoire pour la structure de données : ~15-20 Go (les objets Python sont plus volumineux que le JSON brut)
- Total : ~25-30 Go de RAM
Le streaming réduit cela à quelques mégaoctets.
Approches de streaming
Style SAX (basé sur les événements)
Le parseur émet des événements lorsqu'il rencontre des tokens JSON :
import ijson
# Traiter les éléments un par un - utilisation mémoire constante
with open('huge.json', 'rb') as f:
for record in ijson.items(f, 'records.item'):
process(record) # Each record is parsed individually
# Previous records are garbage collected
Les événements incluent : start_map, map_key, end_map, start_array, end_array, string, number, boolean, null.
JSON Lines (JSONL / NDJSON)
Une approche plus simple : un objet JSON par ligne. Chaque ligne est un document JSON complet et valide :
{"id": 1, "name": "Alice", "email": "alice@example.com"}
{"id": 2, "name": "Bob", "email": "bob@example.com"}
{"id": 3, "name": "Charlie", "email": "charlie@example.com"}
Le traitement est trivial â lire ligne par ligne :
with open('data.jsonl') as f:
for line in f:
record = json.loads(line)
process(record)
Avantages de JSON Lines :
- Chaque ligne est analysable indépendamment (traitement parallÚle)
- Compatible avec l'ajout (il suffit d'ajouter une nouvelle ligne)
- Fonctionne avec les outils Unix standard (
grep,wc,head,tail) - Naturel pour les fichiers de log et les flux de données
Traitement par morceaux
Pour les tableaux JSON standard, divisez le traitement en morceaux :
import ijson
def process_in_chunks(filename, chunk_size=1000):
chunk = []
with open(filename, 'rb') as f:
for record in ijson.items(f, 'item'):
chunk.append(record)
if len(chunk) >= chunk_size:
process_batch(chunk)
chunk = []
if chunk:
process_batch(chunk)
Implémentations par langage
Python (ijson)
import ijson
# Streaming depuis un fichier
with open('large.json', 'rb') as f:
parser = ijson.parse(f)
for prefix, event, value in parser:
if prefix == 'records.item.name':
print(value)
# Streaming depuis une réponse HTTP
import urllib.request
response = urllib.request.urlopen('https://api.example.com/data')
for record in ijson.items(response, 'records.item'):
process(record)
JavaScript (Node.js)
const { createReadStream } = require('fs');
const { parser } = require('stream-json');
const { streamArray } = require('stream-json/streamers/StreamArray');
const pipeline = createReadStream('large.json')
.pipe(parser())
.pipe(streamArray());
pipeline.on('data', ({ value }) => {
process(value);
});
pipeline.on('end', () => {
console.log('Done processing');
});
Ligne de commande (jq)
# Mode streaming - traiter les objets individuellement
jq --stream 'select(length == 2) | .[1]' large.json
# Traiter JSON Lines
cat data.jsonl | jq -c 'select(.age > 30)'
# Convertir un tableau en JSON Lines
jq -c '.[]' large_array.json > data.jsonl
Quand utiliser le streaming
| Scénario | Standard | Streaming |
|---|---|---|
| Fichier de moins de 100 Mo | Préféré | Excessif |
| Fichier de 100 Mo à 1 Go | Dépend de la RAM | Recommandé |
| Fichier de plus de 1 Go | Non viable | Requis |
| Réponse HTTP (volumineuse) | Risque de timeout | Streamer à la réception |
| Flux de données en temps réel | Non applicable | Requis |
| Lecture unique simple | Préféré | Inutile |
Comparaison des performances
Traitement de 1 million d'enregistrements (fichier de 1 Go) :
| Approche | Utilisation mémoire | Temps de traitement | Complexité |
|---|---|---|---|
| json.load() | 3-5 Go | 15 sec | Simple |
| ijson streaming | 50 Mo | 45 sec | Modéré |
| JSON Lines | 10 Mo | 12 sec | Simple |
| Par morceaux (1000) | 100 Mo | 20 sec | Modéré |
Le streaming utilise beaucoup moins de mémoire mais est plus lent pour le parsing de style SAX en raison de la surcharge événementielle. JSON Lines est à la fois rapide et économe en mémoire car chaque ligne est une opération de parsing indépendante.
Bonnes pratiques
- Utilisez JSON Lines quand c'est possible : C'est le format de streaming le plus simple et il fonctionne avec les outils standard.
- Configurez les tailles de tampon : Configurez les tampons de lecture pour un débit optimal (64 Ko à 1 Mo typiquement).
- Traitez par lots : Regroupez les insertions en base de données et les appels API plutÎt que de traiter un enregistrement à la fois.
- Gérez les erreurs avec grùce : En streaming, un enregistrement malformé ne devrait pas faire planter l'ensemble du pipeline.
- Surveillez la mémoire : Utilisez le profilage pour vérifier que le streaming maintient effectivement la mémoire bornée.
Pour formater et valider des fichiers JSON plus petits, notre Formateur JSON gÚre les documents jusqu'à 100 Mo avec un formatage en temps réel.
FAQ
Puis-je utiliser JSONPath avec des parseurs en streaming ?
Certaines bibliothĂšques de streaming supportent le filtrage basĂ© sur le chemin. Python ijson.items supporte le filtrage par chemin pendant le streaming. Cependant, les requĂȘtes JSONPath complexes (filtres, jokers Ă travers les niveaux) nĂ©cessitent gĂ©nĂ©ralement le document complet en mĂ©moire. Pour les requĂȘtes basĂ©es sur le chemin, consultez notre Guide JSONPath.
Comment convertir un gros tableau JSON en JSON Lines ?
Utilisez jq -c '.[]' input.json > output.jsonl pour les fichiers qui tiennent en mémoire. Pour les fichiers vraiment volumineux, utilisez un convertisseur en streaming : lisez le tableau avec un parseur en streaming et écrivez chaque élément comme une ligne.
Ressources connexes
- Formateur JSON â Formatez et validez des fichiers JSON
- Guide des requĂȘtes JSONPath â Extrayez des donnĂ©es du JSON efficacement
- Astuces pour l'Ă©diteur JSON â Travaillez avec de gros documents JSON