Skip to content

Migrating from 2.x to 3.x

Eugene Lazutkin edited this page May 22, 2026 · 6 revisions

Migrating from 2.x to 3.x

stream-json 3.0 is an ESM-only release that rebases the library on stream-chain 4.x and drops CommonJS access.

The runtime APIs, factory shapes, token vocabulary, and per-module export structure are unchanged. Most code only needs the require()import rewrite and Node version bump.

What changed

Area 2.x 3.x
Module type "type": "commonjs" "type": "module"
Minimum Node >=14 (de facto) >=22
stream-chain ^3.6.0 ^4.0.0
Consumer access require() and import import only
Test files (in repo) .mjs / .mts .js / .ts
Main default export make() (parser + emit() bundled) parserStream() (alias of parser.asStream; no emit())
Source layout Flat src/ Tri-tree src/core/ + src/ (Node) + src/web/ (Web)
Assembler shape extends EventEmitter, 'done' event Plain class with onDone callback option
Web Streams entry (not available) stream-json/web + parser.asWebStream() on every Node entry

Tri-tree source layout

3.x mirrors stream-chain v4's core/ + node/ + web/ split:

  • stream-json/parser.js (Node) — the parser factory with both .asStream (Node Duplex) and .asWebStream (Web {readable, writable} pair) attached. Same path you used in 2.x, same API plus the new Web adapter.
  • stream-json/web/parser.js (Web) — the parser factory with only .asWebStream. Importing from stream-json/web/* walks no Node-stream code; safe for browser bundles.
  • stream-json/core/parser.js (pure) — the bare factory, no stream adapters. For consumers composing their own pipelines via stream-chain/core or building custom adapters.
// Node CLI / Bun / Deno — full surface
import {parser} from 'stream-json/parser.js';
parser.asStream(); // Node Duplex
parser.asWebStream(); // Web pair

// Browser-leaning bundle — only Web adapter walked
import {parser} from 'stream-json/web/parser.js';
parser.asWebStream(); // Web pair

// Headline short-form (new in 3.0)
import parserWebStream from 'stream-json/web'; // = parser.asWebStream

Every portable component (parser, disassembler, stringer, filters/*, streamers/*, batch, verifier, with-parser, jsonl/parser, jsonl/stringer, jsonc/*, emitter, utils/emit) ships in both Node (src/) and Web (src/web/) trees. The two SAX-event helpers — emitter and utils/emit — keep substrate-specific subscribe shapes: on Node they use the EventEmitter interface (.on(name, fn)); on Web they use an EventTarget (.addEventListener(name, ev => ev.detail)). Same model — token name as event name, token value as payload — different runtime APIs.

jsonl/stringer is a slim wrapper — the Node side delegates to stream-chain/jsonl/stringerStream (a Node Transform) and the Web side delegates to stream-chain/jsonl/stringerWebStream (a TransformStream). There is no shared core/jsonl/stringer.js since the JSONL stringer maps directly onto a substrate stream rather than a pure flushable factory.

Assembler / FlexAssemblerEventEmitter removed, onDone callback

The 2.x assembler classes extended EventEmitter and emitted a 'done' event each time a top-level value was fully assembled. 3.0 drops the EventEmitter base — both classes are now plain JavaScript classes — and replaces the event with an onDone(asm) callback configured via the constructor option (or asm.onDone(fn) to set/clear later).

// 2.x
import Assembler from 'stream-json/assembler.js';
const asm = Assembler.connectTo(pipeline);
asm.on('done', a => results.push(a.current));

// 3.x
import Assembler from 'stream-json/assembler.js';
const results = [];
Assembler.connectTo(pipeline, {onDone: a => results.push(a.current)});

// Or set the callback later:
const asm = Assembler.connectTo(pipeline);
asm.onDone(a => results.push(a.current));

The internal streamBase (used by streamArray, streamObject, streamValues) never used the event — it inspected asm.current / asm.depth directly via tapChain. So 99% of streaming use cases are unaffected. Only direct connectTo + .on('done', …) consumers need to migrate.

Other EventEmitter API (.once, .removeListener, .emit, etc.) is also gone; if you depended on it, wrap the assembler in your own emitter or use the onDone callback to dispatch.

Main entry: make() removed → parserStream

The 2.x default export of stream-json was make(), a 1.x→2.x bridge that returned a parser as a Duplex stream with emit() already applied so consumers could write stream.on('startObject', …) directly.

In 3.x:

  • The default export is renamed to parserStream — an honest alias for parser.asStream(options).
  • The bundled emit() decoration is dropped. The returned stream is a plain Duplex with {name, value} token data, no synthesized startObject / keyValue / etc. events.
// 2.x
import make from 'stream-json';
const stream = make();
stream.on('startObject', () => {
  /* fires — emit() was bundled */
});

// 3.x
import parserStream from 'stream-json';
const stream = parserStream();
// stream.on('data', ({name, value}) => ...) works as it always did,
// but stream.on('startObject', ...) NO LONGER fires.

If you need the SAX-style named events, wrap the stream with emit() yourself — it's a one-line composition that used to be implicit:

// 3.x — restore the old behavior explicitly
import parserStream from 'stream-json';
import emit from 'stream-json/utils/emit.js';

const stream = emit(parserStream());
stream.on('startObject', () => {
  /* fires */
});

make.parser (the attached 1.x-compat reference) is also gone. Use the named export instead:

// 2.x
import make from 'stream-json';
const {parser} = make; // or const parser = make.parser;

// 3.x
import {parser} from 'stream-json';

Utf8Stream removed

The deprecated Utf8Stream class (stream-json/utils/utf8-stream.js) is gone in 3.0. Use fixUtf8Stream from stream-chain instead:

// 2.x
import Utf8Stream from 'stream-json/utils/utf8-stream.js';
const fixer = new Utf8Stream();

// 3.x
import fixUtf8Stream from 'stream-chain/utils/fixUtf8Stream.js';
const fixer = fixUtf8Stream(); // a flushable, compose via chain([..., fixUtf8Stream(), ...])
// or wrap as a stream:
import {asStream} from 'stream-chain';
const stream = asStream(fixUtf8Stream());

In practice you rarely need this directly — the parser, verifier, and JSONL parser all compose fixUtf8Stream internally on the input side, so multi-byte UTF-8 splits across chunks are handled transparently.

What didn't change

  • The factory pattern for every module (parser(), pick(), streamArray(), verifier(), etc.).
  • .asStream(options), .withParser(options), .withParserAsStream(options) static methods.
  • The token protocol — {name, value} shapes, the token vocabulary, and assembler semantics.
  • TypeScript namespace types like parser.Token, parser.ParserOptions, streamArray.StreamArrayItem continue to work; they are now also re-exported as top-level type names for ESM-style imports.
  • Assembler and FlexAssembler are still classes; Assembler.connectTo(stream, options) works as before — and now also accepts a Web ReadableStream in addition to a Node Readable (substrate is detected automatically).
  • Module paths under stream-json/... are unchanged.

Migration: require()import

The minimal change is a syntactic rewrite. The same module paths and named bindings work.

Main entry point

// 2.x (CJS)
const make = require('stream-json');
const {parser} = require('stream-json');

// 3.x (ESM)
import parserStream from 'stream-json'; // renamed from make; see "Main entry" above
import {parser} from 'stream-json';

Core components

// 2.x
const Assembler = require('stream-json/assembler.js');
const {disassembler} = require('stream-json/disassembler.js');
const stringer = require('stream-json/stringer.js');
const emitter = require('stream-json/emitter.js');

// 3.x
import Assembler from 'stream-json/assembler.js';
import {disassembler} from 'stream-json/disassembler.js';
import stringer from 'stream-json/stringer.js';
import emitter from 'stream-json/emitter.js';

Filters

// 2.x
const {pick} = require('stream-json/filters/pick.js');
const {replace} = require('stream-json/filters/replace.js');
const {ignore} = require('stream-json/filters/ignore.js');
const {filter} = require('stream-json/filters/filter.js');

// 3.x
import {pick} from 'stream-json/filters/pick.js';
import {replace} from 'stream-json/filters/replace.js';
import {ignore} from 'stream-json/filters/ignore.js';
import {filter} from 'stream-json/filters/filter.js';

Streamers

// 2.x
const {streamValues} = require('stream-json/streamers/stream-values.js');
const {streamArray} = require('stream-json/streamers/stream-array.js');
const {streamObject} = require('stream-json/streamers/stream-object.js');

// 3.x
import {streamValues} from 'stream-json/streamers/stream-values.js';
import {streamArray} from 'stream-json/streamers/stream-array.js';
import {streamObject} from 'stream-json/streamers/stream-object.js';

Utilities

// 2.x
const emit = require('stream-json/utils/emit.js');
const withParser = require('stream-json/utils/with-parser.js');
const batch = require('stream-json/utils/batch.js');
const verifier = require('stream-json/utils/verifier.js');
const Utf8Stream = require('stream-json/utils/utf8-stream.js');
const FlexAssembler = require('stream-json/utils/flex-assembler.js');

// 3.x
import emit from 'stream-json/utils/emit.js';
import withParser from 'stream-json/utils/with-parser.js';
import batch from 'stream-json/utils/batch.js';
import verifier from 'stream-json/utils/verifier.js';
import Utf8Stream from 'stream-json/utils/utf8-stream.js';
import FlexAssembler from 'stream-json/utils/flex-assembler.js';

JSONL / JSONC

// 2.x
const jsonlParser = require('stream-json/jsonl/parser.js');
const jsonlStringer = require('stream-json/jsonl/stringer.js');
const jsoncParser = require('stream-json/jsonc/parser.js');
const jsoncStringer = require('stream-json/jsonc/stringer.js');
const jsoncVerifier = require('stream-json/jsonc/verifier.js');

// 3.x
import jsonlParser from 'stream-json/jsonl/parser.js';
import jsonlStringer from 'stream-json/jsonl/stringer.js';
import jsoncParser from 'stream-json/jsonc/parser.js';
import jsoncStringer from 'stream-json/jsonc/stringer.js';
import jsoncVerifier from 'stream-json/jsonc/verifier.js';

Node.js version

3.0 requires Node.js 22 or later, matching stream-chain 4.x. The new minimum reflects the LTS floor at release time and unlocks native web-streams interop in stream-chain (stream-chain/web).

If your project is still on Node 14/16/18/20, stay on stream-json 2.x — it remains the supported branch for CJS / older-Node consumers.

stream-chain 4.x

stream-chain 4.x is the underlying pipeline library. stream-json 3.0 picks up its new module type, ESM exports, and lifted symbols automatically:

  • The default export is still chain.
  • The top-level export surface (asStream, gen, flushable, none, many, combineManyMut, getManyValues, isMany, etc.) is unchanged from the names stream-json consumes.
  • New subpaths (stream-chain/web, stream-chain/core) are available if you want web-streams-based pipelines; stream-json itself remains Node-streams-based.

See the stream-chain 4.0 release notes for details.

Stuck on CJS?

require('stream-json') from a CJS file is no longer supported. Three options:

  1. Convert the consumer to ESM. Add "type": "module" to your package.json and switch require()import. The same module paths still resolve.
  2. Use Node 22.12+ require(esm) flag. Recent Node versions can require() an ESM module under --experimental-require-module. Not a long-term recommendation.
  3. Stay on stream-json 2.x. It remains available on npm and is the maintained CJS line.

TypeScript

Type imports continue to work. Both styles are supported:

// Namespace style (worked in 2.x, still works in 3.x)
import parser from 'stream-json/parser.js';
const t: parser.Token = {name: 'startObject'};
const opts: parser.ParserOptions = {jsonStreaming: true};

// Named-import style (recommended in 3.x)
import {parser, type Token, type ParserOptions} from 'stream-json/parser.js';
const t: Token = {name: 'startObject'};
const opts: ParserOptions = {jsonStreaming: true};

Set your project's tsconfig.json to "module": "node16" (or "nodenext") and "moduleResolution": "node16" so that the .js extensions in stream-json's type declarations resolve correctly.

See also

Clone this wiki locally