streamparser-json

repository·main·Indexed 42 days ago

https://github.com/juanjodiaz/streamparser-json

A high-performance, spec-compliant streaming JSON parser for JavaScript, supporting Node.js, Deno, and the browser. It provides a core pure JavaScript implementation (@streamparser/json) with specialized wrappers for Node.js Transform streams (@streamparser/json-node) and WHATWG TransformStreams (@streamparser/json-whatwg). Version 0.0.26 allows for memory-efficient parsing of large JSON arrays and streams using JSONPath-like selectors and configurable buffering.

Tokens
9K
Snippets
28
Records
44
Agent score
49%

What's inside streamparser-json

  1. Overview of @streamparser/json

    main
    @streamparser/json is a fast, dependency-free library designed to parse JSON streams using UTF-8 encoding. It is compatible with Node.js, Deno, and modern web browsers. The parser is fully compliant with the JSON specification and behaves identically to JSON.parse(...) for the data it processes.
  2. Compare @streamparser/json with JSON.parse

    main

    While JSON.parse() is the standard for synchronous JSON parsing, @streamparser/json provides several advantages for streaming and high-performance use cases:

    • Performance & Environment: As performant as (or faster than) JSON.parse() and works in the browser.
    • Selective Emission: Allows you to use selectors to emit only specific parts of the JSON structure.
    • Spec Compliance: Fully compliant with the JSON spec (unlike JSONparse, which incorrectly allowed trailing commas or failed on certain character ranges).
    • Architecture: Separates the tokenizer and token parser processes, making it more pluggable and configurable.
    • Modern Codebase: Uses ES6 and avoids deprecated Node.js methods.
    • Error Handling: Defaults to throwing errors during synchronous write operations (to be caught via try-catch), though an onError callback can be provided to prevent throwing.
  3. Understand backpressure in @streamparser/json-node

    main

    The parser participates in standard Node.js stream backpressure. If a downstream consumer is slow, the source is throttled.

    Important Caveat: A single write() call is processed in one synchronous pass. This means all values contained within that one chunk are emitted at once. To ensure effective backpressure for large inputs, feed the parser in chunks (e.g., by piping from a file or socket) rather than passing a single large string or buffer in one write() call.

  4. Understand the Throughput benchmark methodology

    main

    The throughput suite measures parsing performance in MB/s (decimal, 10⁶ B/s) by parsing representative JSON into materialized values. It compares the three public APIs—the core JSONParser, the Node Transform, and the WHATWG TransformStream—against native JSON.parse across four engines.

    Key Concepts

    • Corpus: Uses 8 deterministic generators (~2 MB each) covering various JSON shapes (GeoJSON, deeply-nested catalogs, unicode-heavy feeds, NDJSON log streams, etc.) to mirror real-world production data.
    • Feed Modes:
      • whole-blob: A single write operation representing the pure-parse upper bound (core + JSON.parse only).
      • 16 KB chunks: Represents the realistic streaming path used by adapters. Note that feeding a TransformStream a single multi-MB blob is not a valid test for streaming performance as it hits Node stream-queue pathologies.
    • Metrics: Results are recorded in throughput/results.json. The suite uses adaptive warmup, median of N samples, and forced GC between samples. A CV% (Coefficient of Variation) greater than 15% is flagged with ! to indicate noisy data.
    • Comparison: Because streaming parsers incur a performance tax compared to native C++ JSON.parse, the suite focuses on the native-relative ratio as the portable performance metric rather than absolute MB/s.
  5. Understand backpressure in @streamparser/json-whatwg

    main

    The parser integrates with standard WHATWG stream backpressure. If a downstream consumer is slow, the writer is throttled (its desiredSize drops), ensuring only a bounded number of parsed values are held in the readable buffer at a time. You can use standard pipeThrough or pipeTo methods to manage this.

    Important Caveat: Backpressure applies between chunks, not within a single chunk. A single write() call is processed in one synchronous pass, meaning all values contained within that specific chunk are emitted and buffered immediately. To ensure effective backpressure for large inputs, feed the data in chunks (e.g., by piping from a fetch or file stream) rather than passing a single large document as one write() call.

  6. Understand the @streamparser ecosystem

    main

    The @streamparser ecosystem provides different flavors of the parser depending on your runtime and stream requirements:

    • @streamparser/json: Pure JavaScript implementation for parsing any JSON string or stream.
    • @streamparser/json-whatwg: A wrapper around @streamparser/json that implements the WHATWG TransformStream interface.
    • @streamparser/json-node: A wrapper around @streamparser/json that implements a Node.js Transform stream.
  7. Manage stream processing lifecycle with end() and onEnd

    main

    Unlike JSONparse, which parses all valid objects in a stream, @streamparser/json is designed to stop after a single object unless a separator is explicitly configured.

    • Ending Processing: Use the end() method to stop processing. If the stream is in the middle of parsing an incomplete or incorrect JSON structure, end() will throw an error.
    • Completion Callback: Use the onEnd callback to execute logic when the processing successfully finishes.
    • Single Number Support: Calling end() ensures that any number currently being parsed is emitted before the process terminates.
  8. Choose the right @streamparser flavor for your environment

    main

    The @streamparser ecosystem provides different packages depending on how you want to consume the stream:

    • @streamparser/json: The core package. Use this to parse any JSON string or stream using pure JavaScript logic. This is the base implementation.
    • @streamparser/json-whatwg: Use this if you are working in an environment that supports WHATWG Streams (like modern browsers or Deno) and want to wrap the parser into a TransformStream.
    • @streamparser/json-node: Use this if you are working in Node.js and want to wrap the parser into a standard Node.js Transform stream.
  9. Understand the @streamparser/json ecosystem

    main

    The @streamparser ecosystem provides different wrappers for the core parsing logic depending on your environment:

    • @streamparser/json: Pure JavaScript implementation for parsing any JSON string or stream.
    • @streamparser/json-whatwg: Wraps the core logic into a WHATWG TransformStream (ideal for Browsers, Deno, and modern Node.js).
    • @streamparser/json-node: Wraps the core logic into a Node.js Transform stream.
  10. Quickstart: Parse a JSON stream with JSONParser

    main

    The easiest way to use @streamparser/json-whatwg is via the JSONParser class, which acts as a WHATWG TransformStream. You can pipe a ReadableStream (like a file stream or a fetch response body) through the parser to receive parsed JSON values.

    Values can be retrieved by piping to a destination stream or by manually using a reader to iterate through the stream.

    import { JSONParser } from '@streamparser/json-whatwg';
    
    const inputStream = new ReadableStream({
      async start(controller) {
        controller.enqueue('{ "test": ["a"] }');
        controller.close();
      },
    });
    
    const parser = new JSONParser();
    // Option 1: Pipe to a destination
    const reader = inputStream.pipeThrough(parser).getReader();
    
    // Option 2: Manually reading values
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      processValue(value);
      // For the input above, values emitted: "a", ["a"], { test: ["a"] }
    }
  11. Quickstart with @streamparser/json-node

    main

    To quickly parse a JSON stream in Node.js, import JSONParser from @streamparser/json-node. You can use it by piping an input stream into the parser and then piping the parser into a destination stream, or by listening to the data, error, and end events to process values as they are parsed.

    import { JSONParser } from '@streamparser/json-node';
    
    const parser = new JSONParser();
    
    // Using pipes
    inputStream.pipe(parser).pipe(destinationStream);
    
    // Or using events
    parser.on("data", (value) => { /* ... */ });
    parser.on("error", err => { /* ... */ });
    parser.on("end", () => { /* ... */ });
  12. Configure @streamparser/json for JSONparse-like behavior

    main

    If you need to replicate specific behaviors or constraints found in the older JSONparse library, you can use the following configuration options:

    • Memory Management: To avoid memory exhaustion with very long strings (replicating JSONparse's buffer strategy), set the stringBufferSize option to 64 * 1024.
    • Number Parsing: To handle large numbers as strings (which is non-compliant with the spec but sometimes required), override the parseNumber method.
    • Handling Single Numbers: To ensure a single valid JSON number is emitted correctly, use the end() method to complete the processing.
    // Example: Replicating JSONparse memory constraints
    const parser = new StreamParser({
      stringBufferSize: 64 * 1024
    });