Skip to content

trananhtung/jsonlkit

Repository files navigation

jsonlkit

All Contributors

Stream-friendly NDJSON / JSON Lines parsing and serialization — for strings, ReadableStreams, and async iterables. Zero dependencies.

CI npm version bundle size types license

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, a ReadableStream<Uint8Array | string>, or any (async) iterable, decoding bytes with TextDecoder — 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 JSONLParseError carries the 1-based line number and raw text; or set skipInvalid to tolerate bad lines (with an onError hook).
  • Round-trips. toJSONL / stringifyJSONL write 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/jsonlkit

API

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 records

Companion 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:

Tung Tran
Tung Tran

💻 🚧

License

MIT © Tung Tran

About

Stream-friendly NDJSON / JSON Lines parsing and serialization for strings, ReadableStreams, and async iterables. Zero dependencies.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors