Skip to content
Eugene Lazutkin edited this page Jun 7, 2026 · 2 revisions

(Since 3.3.0) stringerToFile() is a Node-only, file-based output-edge sink stage for stream-json pipelines. Drop it at the tail of a gen([…]) chain and the pipeline writes its token stream straight to disk via fs/promises. The merged write path avoids the Node Duplex boundary that the idiomatic chain([…, stringer()]).pipe(createWriteStream) pays.

Introduction

import {parseFile} from 'stream-json/file/parser.js';
import {stringerToFile} from 'stream-json/file/stringer.js';
import {pipe} from 'stream-chain/utils/pipe.js';
import {drain} from 'stream-chain/utils/drain.js';

// file → tokens → file (verbatim)
await drain(pipe(parseFile(), stringerToFile('out.json'))('in.json'));

JSONC variant (round-trips comments and trailing commas):

import {stringerToFile} from 'stream-json/file/jsonc/stringer.js';

How it works

stringerToFile(path, options) returns gen(stringer(options), asyncBlockWriter(path, options)) — an fList that drops as the last stage in a gen([…]) chain. The asyncBlockWriter is a flushable: it accumulates the stringer's per-token string output into an in-memory buffer and writes whole writeBlockSize-sized blocks to a FileHandle via fh.write. The handle is closed on the writer's final() — which is signaled by passing none through the pipe. pipe(...) from stream-chain/utils/pipe.js does the flush after the data pass so the file terminates cleanly without ceremony.

stringerToFile(path, options?)

  • path — the output file. Created if missing, truncated if it exists.
  • Returns an fList usable as a chain stage.

Options (combined stringer + write-block):

  • writeBlockSize — write-block size in bytes. Default 1048576 (1 MB). A memory/syscall knob.
  • All stringer() options pass through: useValues, useKeyValues, useStringValues, useNumberValues, makeArray.

Also exported as stringer (matching the file name file/stringer.js), so import {stringer} from 'stream-json/file/stringer.js' works too.

Driving the chain — pipe is required for flush

gen doesn't flush on its own. Sink stages like stringerToFile's writer need a none signal to write their remaining buffer and close the file. pipe(...) is the one-shot driver that does the data pass AND the flush; drain(...) consumes the resulting async generator and resolves to undefined (the writer yields nothing).

const c = pipe(parseFile(), pick({filter: 'items'}), streamArray(), stringerToFile('out.json'));
await drain(c('in.json'));

Performance

See bench/file-roundtrip.js. The merged round-trip (pipe(parseFile(), stringerToFile())) is roughly 1.6× faster than the idiomatic chain([createReadStream, parser(), stringer()]).pipe(createWriteStream) (≈30 ms vs ≈50 ms median for a 100 KB fixture on Intel i3‑10110U, Node 26). The win comes from avoiding the Node Duplex boundary between the stringer and the file write side.

Related

  • parseFile — symmetric input-edge source.
  • Stringer — the underlying token-to-text flushable.

Clone this wiki locally