Blazing-fast JSON repair for LLM streaming
When streaming responses from LLMs like OpenAI or Anthropic, JSON often arrives incomplete:
{"message": "Hello, I'm currently generating your respJSON.parse() crashes. repair-json-stream fixes it instantly.
import { repairJson } from 'repair-json-stream'
repairJson('{"message": "Hello, I\'m currently generating your resp')
// β '{"message": "Hello, I\'m currently generating your resp"}'Built for real-time streaming. 1.9x faster than alternatives.
ββββββββββββββββββββββ¬βββββββββββββββββββββ¬βββββββββββββ¬ββββββββββ
β Benchmark β repair-json-stream β jsonrepair β Speedup β
ββββββββββββββββββββββΌβββββββββββββββββββββΌβββββββββββββΌββββββββββ€
β Small (15 KB) β 0.85 ms β 1.53 ms β 1.8x β
β Large (3.9 MB) β 295 ms β 392 ms β 1.3x β
β Streaming (1K ops) β 426 ms β 583 ms β 1.4x β
ββββββββββββββββββββββ΄βββββββββββββββββββββ΄βββββββββββββ΄ββββββββββ
npm install repair-json-streampnpm add repair-json-streamyarn add repair-json-stream| Issue | Example | Result |
|---|---|---|
| Truncated strings | {"text": "Hello |
{"text": "Hello"} |
| Missing brackets | {"a": [1, 2 |
{"a": [1, 2]} |
| Unquoted keys | {name: "John"} |
{"name": "John"} |
| Single quotes | {'key': 'val'} |
{"key": "val"} |
| Python constants | {"x": None, "y": True} |
{"x": null, "y": true} |
| Trailing commas | [1, 2, 3,] |
[1, 2, 3] |
| Comments | {"a": 1} // comment |
{"a": 1} |
| Fenced code blocks | ```json {"a":1} ``` |
{"a":1} |
| JSONP wrappers | callback({"a": 1}) |
{"a": 1} |
| MongoDB types | NumberLong(123) |
123 |
| String concatenation | "hello" + "world" |
"helloworld" |
| NDJSON | {"a":1}\n{"b":2} |
[{"a":1},{"b":2}] |
import { repairJson } from 'repair-json-stream'
const broken = '{"users": [{"name": "Alice'
const fixed = repairJson(broken)
// β '{"users": [{"name": "Alice"}]}'
JSON.parse(fixed) // Works!import { createReadStream, createWriteStream } from 'fs'
import { pipeline } from 'stream'
import { jsonrepairTransform } from 'repair-json-stream/stream'
pipeline(
createReadStream('broken.json'),
jsonrepairTransform(),
createWriteStream('fixed.json'),
(err) => console.log(err || 'Done!')
)# Install globally
npm install -g repair-json-stream
# Repair a file
repair-json-stream broken.json > fixed.json
# Pipe from stdin
cat broken.json | repair-json-stream
# Overwrite in place
repair-json-stream broken.json --overwrite
# Verbose output (show repair actions)
repair-json-stream broken.json --verbose<script src="https://unpkg.com/repair-json-stream/dist/repair-json-stream.browser.global.js"></script>
<script>
const fixed = RepairJsonStream.repairJson('{"broken": true')
console.log(fixed) // '{"broken": true}'
</script>Repairs broken JSON and returns valid JSON. Never throws.
repairJson('{"a": tru') // '{"a": true}'
repairJson("{'b': None}") // '{"b": null}'
repairJson('[1, 2,') // '[1, 2]'Track what was fixed using the callback:
repairJson('{"a": 1,', (action, idx, context) => {
console.log(`Action: ${action} at index ${idx} (${context})`)
})
// Output: Action: closed_object at index 7 (Closing missing })Strips wrappers (fenced blocks, JSONP, escaped strings) before repair.
preprocessJson('```json\n{"a": 1}\n```') // '{"a": 1}'
preprocessJson('callback({"a": 1})') // '{"a": 1}'Node.js Transform stream for piping.
import { jsonrepairTransform } from 'repair-json-stream/stream'Universal TransformStream for Deno, Bun, Cloudflare Workers, and modern browsers.
import { jsonRepairStream } from 'repair-json-stream/web-stream'
// Use with fetch
const response = await fetch('/api/llm')
const repairedStream = response.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(jsonRepairStream())
// Read repaired JSON
const reader = repairedStream.getReader()
const { value } = await reader.read()
JSON.parse(value) // Always valid!True streaming repair with immediate partial output. Perfect for live UI updates.
import { IncrementalJsonRepair } from 'repair-json-stream/incremental'
const repairer = new IncrementalJsonRepair()
// As LLM streams chunks...
let output = ''
for await (const chunk of llmStream) {
output += repairer.push(chunk)
updateUI(output) // Live update!
}
output += repairer.end()
JSON.parse(output) // Always valid!Methods:
.push(chunk)- Process chunk, returns repaired output.end()- Finalize and close open structures.snapshot()- Get valid JSON at any point (non-destructive).reset()- Reset state for reuse
Extract JSON from messy LLM outputs containing prose, thinking blocks, or markdown.
import { extractJson, extractAllJson, stripLlmWrapper } from 'repair-json-stream/extract'
// Extract JSON from prose
extractJson('Sure! Here is the data: {"name": "John"} Hope this helps!')
// β '{"name": "John"}'
// Handle thinking blocks (DeepSeek, Claude, etc.)
extractJson('<thought>Let me think...</thought>\n{"result": true}')
// β '{"result": true}'
// Extract multiple JSON blocks
extractAllJson('First: {"a": 1} Second: {"b": 2}')
// β ['{"a": 1}', '{"b": 2}']
// Full cleanup (markdown, prose, thinking blocks)
stripLlmWrapper(`
<thinking>reasoning here</thinking>
\`\`\`json
{"data": [1, 2, 3]}
\`\`\`
Let me know if you need anything else!
`)
// β '{"data": [1, 2, 3]}'- O(n) single-pass - No multiple iterations
- Stack-based state machine - No regex parsing
- Zero dependencies - Minimal attack surface
- ReDoS-safe - No exponential backtracking
- 5.5 KB minified - Lightweight for browsers
MIT Β© sonka
