json-formatieren.de

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.

Foto von Jan-Tristan Rudat

Von Jan-Tristan Rudat

Redakteur json-formatieren.de

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

LibraryStyleBest for
stream-jsonNode-StreamsPipeline-Architekturen, große Arrays
oboeJSONPath-basiertBrowser, partial DOM-Trees
clarinetSAX-Stylevolle Kontrolle, low-level
jq (CLI)--stream-ModeAd-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

  1. "Stream und dann alles wieder in ein Array pushen": du hast einen DOM-Parser nachgebaut, nur langsamer.
  2. Streaming für API-Responses unter 5 MB: Code-Komplexität ohne Memory-Nutzen.
  3. Synchrones Streaming: die Libraries sind alle async - Promise-basiert oder Streams. Wer das ignoriert, blockiert den Event-Loop.

Mehr zum Thema