mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
Adds a built-in JSONL parser implemented in C++ using JavaScriptCore's
optimized JSON parser.
## API
### `Bun.JSONL.parse(input)`
Parse a complete JSONL string or `Uint8Array` and return an array of all
parsed values. Throws on invalid input.
```ts
const results = Bun.JSONL.parse('{"a":1}\n{"b":2}\n');
// [{ a: 1 }, { b: 2 }]
```
### `Bun.JSONL.parseChunk(input, start?, end?)`
Parse as many complete values as possible, returning `{ values, read,
done, error }`. Designed for streaming use cases where input arrives
incrementally.
```ts
const result = Bun.JSONL.parseChunk('{"id":1}\n{"id":2}\n{"id":3');
result.values; // [{ id: 1 }, { id: 2 }]
result.read; // 17
result.done; // false
result.error; // null
```
## Implementation Details
- C++ implementation in `BunObject.cpp` using JSC's `streamingJSONParse`
- ASCII fast path: zero-copy `StringView` for pure ASCII input
- Non-ASCII: uses `fromUTF8ReplacingInvalidSequences` with
`utf16_length_from_utf8` size check to prevent overflow
- UTF-8 BOM automatically skipped for `Uint8Array` input
- Pre-built `Structure` with fixed property offsets for fast result
object creation
- `Symbol.toStringTag = "JSONL"` on the namespace object
- `parseChunk` returns errors in `error` property instead of throwing,
preserving partial results
- Comprehensive boundary checks on start/end parameters
## Tests
234 tests covering:
- Complete and partial/streaming input scenarios
- Error handling and recovery
- UTF-8 multi-byte characters and BOM handling
- start/end boundary security (exhaustive combinations, clamping, OOB
prevention)
- 4 GB input rejection (both ASCII and non-ASCII paths)
- Edge cases (empty input, single values, whitespace, special numbers)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
80 lines
2.6 KiB
TypeScript
80 lines
2.6 KiB
TypeScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
import { describe, expect, test } from "bun:test";
|
|
import { readdirSync } from "fs";
|
|
import "harness";
|
|
import { tmpdirSync } from "harness";
|
|
import path from "path";
|
|
describe("doesnt_crash", async () => {
|
|
let files: string[] = [];
|
|
let temp_dir: string = tmpdirSync();
|
|
const files_dir = path.join(import.meta.dir, "files");
|
|
temp_dir = tmpdirSync();
|
|
files = readdirSync(files_dir).map(file => path.join(files_dir, file));
|
|
console.log("Tempdir", temp_dir);
|
|
|
|
files.forEach(absolute => {
|
|
absolute = absolute.replaceAll("\\", "/");
|
|
const file = path.basename(absolute);
|
|
|
|
const configs: { target: string; minify: boolean }[] = [
|
|
{ target: "bun", minify: false },
|
|
{ target: "bun", minify: true },
|
|
{ target: "browser", minify: false },
|
|
{ target: "browser", minify: true },
|
|
];
|
|
let code = "";
|
|
async function getCode() {
|
|
if (code) return code;
|
|
code = await Bun.file(absolute).text();
|
|
return code;
|
|
}
|
|
|
|
for (const { target, minify } of configs) {
|
|
test(`${file} - ${minify ? "minify" : "not minify"} - ${target}`, async () => {
|
|
const timeLog = `Transpiled ${file} - ${minify ? "minify" : "not minify"}`;
|
|
console.time(timeLog);
|
|
const { logs, outputs } = await Bun.build({
|
|
entrypoints: [absolute],
|
|
minify: minify,
|
|
target,
|
|
files: { [absolute]: await getCode() },
|
|
});
|
|
console.timeEnd(timeLog);
|
|
|
|
if (logs?.length) {
|
|
throw new Error(logs.join("\n"));
|
|
}
|
|
|
|
expect(outputs.length).toBe(1);
|
|
const outfile1 = path.join(temp_dir, "file-1" + file).replaceAll("\\", "/");
|
|
const content1 = await outputs[0].text();
|
|
|
|
await Bun.write(outfile1, outputs[0]);
|
|
|
|
{
|
|
const timeLog = `Re-transpiled ${file} - ${minify ? "minify" : "not minify"}`;
|
|
console.time(timeLog);
|
|
console.log(" Transpiled file path:", outfile1);
|
|
const { logs, outputs } = await Bun.build({
|
|
entrypoints: [outfile1],
|
|
target,
|
|
files: { [outfile1]: content1 },
|
|
minify: minify,
|
|
});
|
|
|
|
if (logs?.length) {
|
|
throw new Error(logs.join("\n"));
|
|
}
|
|
|
|
expect(outputs.length).toBe(1);
|
|
expect(await outputs[0].text()).not.toBeEmpty();
|
|
console.timeEnd(timeLog);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|