Ratgeber · JSON 2026
JSON-Streaming: 500 MB Datei ohne OutOfMemoryError verarbeiten
Für API-Logs oder Export-Dumps reicht JSON.parse nicht. Streaming-Parser arbeiten Token-für-Token und halten den RSS klein.
Warum JSON.parse irgendwann nicht mehr reicht
JSON.parse ist ein DOM-Parser: er liest das gesamte Dokument in den Speicher und baut einen vollständigen JavaScript-Objekt-Baum. Der Speicherbedarf entspricht typisch 2-3× der Datei-Größe - bei 500 MB JSON also 1-1,5 GB RAM. Für Cloud-Instanzen unter 2 GB RAM ist das ein hartes Limit.
Streaming-Parser arbeiten event-basiert: sie emittieren während des Parsings Tokens (onkey, onvalue, oncloseobject), ohne den Baum komplett zu materialisieren. Memory bleibt konstant - auch bei 100 GB Input.
Die wichtigsten Libraries
| Library | Style | Best for |
|---|---|---|
| stream-json | Node-Streams | Pipeline-Architekturen, große Arrays |
| oboe | JSONPath-basiert | Browser, partial DOM-Trees |
| clarinet | SAX-Style | volle Kontrolle, low-level |
| jq (CLI) | --stream-Mode | Ad-hoc-Shell-Pipelines |
stream-json: das pragmatische Default
stream-json (von uhop) ist die meistgenutzte Streaming-Library in Node.js. Sie integriert sich nahtlos in die Node-Stream-Pipeline:
import { parser } from 'stream-json';
import { streamArray } from 'stream-json/streamers/StreamArray.js';
import { chain } from 'stream-chain';
const pipeline = chain([
fs.createReadStream('dump.json'),
parser(),
streamArray(),
data => processEvent(data.value),
]);
pipeline.on('end', () => console.log('done'));
Funktioniert für JSON-Dokumente die ein Top-Level-Array von Items sind (häufiger Case bei Exports). Für Objects mit großen Inner-Arrays gibt es streamObject.
oboe: progressives Parsing im Browser
oboe.js ist die Browser-Variante. Sie erlaubt JSONPath-artige Selectoren um Teil-Bäume während des Downloads bereits zu verarbeiten:
oboe('/api/large-result.json')
.node('!.items[*]', function(item) {
addToList(item); // wird pro Item gerufen, schon während Download läuft
})
.done(function() { console.log('all loaded'); });
jq --stream im Shell
Für Ad-hoc-Verarbeitung großer Files in der Shell bietet jq den --stream-Modus:
jq --stream 'select(length==2) | select(.[0][0]=="users")' big.json
Output ist eine Sequenz von [path, value]-Tupeln statt eines Baums. Etwas gewöhnungsbedürftig, aber speicher-schonend.
Tradeoffs
- Random-Access fällt weg - Streaming-Parser sehen jedes Element nur einmal
- Cross-References zwischen weit auseinander liegenden Elementen brauchen eigene State-Verwaltung
- Schema-Validation ist eingeschränkt - Ajv arbeitet auf vollständigen Objekten
- Sortierungen über das gesamte Dokument funktionieren nicht streaming-basiert
Wann lohnt es sich?
Faustregel: ab ~50 MB Datei-Größe oder wenn das Memory-Limit der Laufzeit ≤ 3× der Datei-Größe ist. Bei kleineren Files wirkt JSON.parse oft schneller, weil der Streaming-Overhead die Speicherersparnis übersteigt.
Anti-Patterns
- "Stream und dann alles wieder in ein Array pushen": du hast einen DOM-Parser nachgebaut, nur langsamer.
- Streaming für API-Responses unter 5 MB: Code-Komplexität ohne Memory-Nutzen.
- Synchrones Streaming: die Libraries sind alle async - Promise-basiert oder Streams. Wer das ignoriert, blockiert den Event-Loop.