@billdaddy/jsonlkit
v0.1.1
Published
Stream-friendly NDJSON / JSON Lines parsing and serialization for strings, ReadableStreams, and async iterables. Zero dependencies.
Maintainers
Readme
jsonlkit
Stream-friendly NDJSON / JSON Lines parsing and serialization — for strings,
ReadableStreams, and async iterables. Zero dependencies.
NDJSON (a.k.a. JSON Lines) is everywhere — LLM batch outputs, structured
logs, data exports, dataset shards. Parsing it correctly from a stream means
handling lines split across network chunks, multi-byte characters cut in half,
CRLF, a BOM, blank lines, and a final record with no trailing newline.
jsonlkit handles all of that and reads straight from a fetch body.
import { parseJSONL } from "@billdaddy/jsonlkit";
const res = await fetch(batchOutputUrl);
for await (const row of parseJSONL<ResultRow>(res.body!)) {
process(row); // one parsed record at a time, constant memory
}Why jsonlkit?
- Stream-native. Reads a
string, aReadableStream<Uint8Array | string>, or any (async) iterable, decoding bytes withTextDecoder— even when a multi-byte character is split across chunks. - Correct edges.
\n/\r\n, leading BOM, blank lines skipped, and a final line without a newline still parsed. - Errors you can act on. A
JSONLParseErrorcarries the 1-based line number and raw text; or setskipInvalidto tolerate bad lines (with anonErrorhook). - Round-trips.
toJSONL/stringifyJSONLwrite records back out, line by line. - Zero dependencies, ESM + CJS + types, and a CLI.
Install
npm install @billdaddy/jsonlkit
# or: pnpm add @billdaddy/jsonlkit / yarn add @billdaddy/jsonlkit / bun add @billdaddy/jsonlkitAPI
parseJSONL(source, options?) → AsyncGenerator<T>
for await (const row of parseJSONL<MyRow>(source, { skipInvalid: true })) { … }| Option | Type | Default | Description |
| ------------- | ------------------------------------- | ------- | ---------------------------------------- |
| skipInvalid | boolean | false | Skip unparseable lines instead of throwing. |
| onError | (error, line, lineNumber) => void | — | Observe each parse error. |
| reviver | (key, value) => unknown | — | Forwarded to JSON.parse. |
toJSONL(items, options?) → string
toJSONL([{ a: 1 }, { b: 2 }]); // '{"a":1}\n{"b":2}\n'stringifyJSONL(items, options?) → AsyncGenerator<string>
Yields one NDJSON line per record (accepts sync or async iterables) so you can pipe to a writable stream without buffering everything.
createParser(onValue, options?)
The low-level push parser: feed(chunk) for each chunk, then end() to flush a
trailing line.
const parser = createParser((row) => handle(row));
socket.on("data", (b) => parser.feed(b.toString("utf8")));
socket.on("end", () => parser.end());CLI
cat data.jsonl | jsonlkit # validate + re-emit compact NDJSON
cat data.jsonl | jsonlkit --pretty # expand each record
cat data.jsonl | jsonlkit --count # count valid recordsCompanion packages
Part of the same streaming toolkit as
ssekit (Server-Sent Events) and
jsonpluck (rescue JSON from messy LLM output).
Contributors ✨
This project follows the all-contributors specification. Contributions of any kind are welcome — code, docs, bug reports, ideas, reviews! See the emoji key for how each contribution is recognized, and open a PR or issue to get involved.
Thanks goes to these wonderful people:
License
MIT © Tung Tran
