-
-
Notifications
You must be signed in to change notification settings - Fork 53
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.
| 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 |
3.x mirrors stream-chain v4's core/ + node/ + web/ split:
-
stream-json/parser.js(Node) — the parser factory with both.asStream(NodeDuplex) 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 fromstream-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 viastream-chain/coreor 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.asWebStreamEvery 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.
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.
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 forparser.asStream(options). - The bundled
emit()decoration is dropped. The returned stream is a plain Duplex with{name, value}token data, no synthesizedstartObject/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';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.
- 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.StreamArrayItemcontinue to work; they are now also re-exported as top-level type names for ESM-style imports. -
AssemblerandFlexAssemblerare still classes;Assembler.connectTo(stream, options)works as before — and now also accepts a WebReadableStreamin addition to a NodeReadable(substrate is detected automatically). - Module paths under
stream-json/...are unchanged.
The minimal change is a syntactic rewrite. The same module paths and named bindings work.
// 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';// 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';// 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';// 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';// 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';// 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';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 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 namesstream-jsonconsumes. - New subpaths (
stream-chain/web,stream-chain/core) are available if you want web-streams-based pipelines;stream-jsonitself remains Node-streams-based.
See the stream-chain 4.0 release notes for details.
require('stream-json') from a CJS file is no longer supported. Three options:
-
Convert the consumer to ESM. Add
"type": "module"to yourpackage.jsonand switchrequire()→import. The same module paths still resolve. -
Use Node 22.12+
require(esm)flag. Recent Node versions canrequire()an ESM module under--experimental-require-module. Not a long-term recommendation. -
Stay on
stream-json2.x. It remains available on npm and is the maintained CJS line.
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.
-
Migrating from 1.x to 2.x — the previous major change (functional API,
src/reorg). - Migrating from 0.x to 1.x — historical reference.
- Release history.
Start here
Core
Filters
Streamers
Essentials
Utilities
File I/O (Node-only)
JSONC
JSONL (use stream-chain)
Reference
Built on stream-chain