npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

safe-json-repair

v0.1.1

Published

A JSON repair library that never throws and never silently drops data — keeps the sibling keys LLMs orphan with a stray closing delimiter.

Readme

safe-json-repair

Repair broken JSON from LLMs — never throws, never silently drops a field. One synchronous call, runs everywhere (Node, Bun, Deno, browsers, edge).

npm i safe-json-repair      # or: bun add / pnpm add / yarn add
import { repairJson } from 'safe-json-repair';

const r = repairJson('{"a":1}, "b":2}');   // a stray "}" closed the root early
r.ok;        // true
r.value;     // { a: 1, b: 2 }      ← "b" is NOT lost
r.json;      // '{"a":1,"b":2}'
r.strategy;  // 'tolerant'

What problem this solves

LLMs in tool-calling / structured-output mode (DeepSeek, OpenAI, …) routinely return JSON with a stray closing delimiter that closes the parent or root object too early, orphaning every field that comes after it. On that exact shape, the popular repair libraries fail:

| Library | On {"a":1}, "b":2} | |---------|----------------------| | jsonrepair (1.78M/wk) | throws or mangles it | | @qraftr/json-repair, jaison, json-repair-js | recover structure but silently drop "b" | | safe-json-repair | keeps "b" ✅ |

Dropping a field is the dangerous failure: your tool call silently loses data and you never find out. This library is built so that never happens.

Usage

The full result

import { repairJson } from 'safe-json-repair';

const r = repairJson(maybeBrokenJson);

if (r.ok) {
  doSomething(r.value);          // recovered value (already a JS object)
} else {
  // Only happens for unrepairable garbage. r.value is the fallback (null by default).
  log.warn('could not repair JSON', { strategy: r.strategy });
}

Convenience wrappers

import { parseJsonSafe, repairJsonString } from 'safe-json-repair';

// Just the value, typed — or `undefined` if nothing could be recovered.
const args = parseJsonSafe<ToolArgs>(brokenToolArgs);

// Just the repaired JSON string — or `null`.
const json = repairJsonString(brokenToolArgs);

Real-world: guarding an LLM tool call

import { parseJsonSafe } from 'safe-json-repair';

function readToolArgs(raw: string): ToolArgs {
  // The model usually returns valid JSON (fast path, zero rewrite). When it
  // doesn't, we recover instead of crashing the whole turn — and we keep every
  // field, so the downstream tool gets complete arguments.
  const args = parseJsonSafe<ToolArgs>(raw, { fallback: 'empty-object' });
  if (!args) throw new Error('tool args unrecoverable');
  return args;
}

repairJson is synchronous and never throws — the WebAssembly module is compiled once, lazily, on the first call. Any input, including pure garbage, returns a deterministic RepairResult.

API

function repairJson(input: string, options?: RepairOptions): RepairResult;
function parseJsonSafe<T = unknown>(input: string, options?: RepairOptions): T | undefined;
function repairJsonString(input: string, options?: RepairOptions): string | null;

interface RepairOptions {
  maxLen?: number;                     // default 5_000_000; larger input → fallback (no work)
  fallback?: 'null' | 'empty-object';  // what to return when nothing parses. default 'null'
  stripCodeFences?: boolean;           // strip ```json … ``` wrappers. default true
  unwrapDoubleEncoded?: boolean;       // unwrap a JSON string that holds an object/array. default true
}

interface RepairResult<T = unknown> {
  ok: boolean;        // false only when it fell through to fallback
  value: T;           // the recovered value (the fallback value when !ok)
  json: string;       // canonical JSON serialization of `value`
  changed: boolean;   // was the original input altered to produce this?
  strategy: Strategy; // which rung of the ladder produced the result
}

type Strategy =
  | 'parse' | 'strip-fences' | 'strip-controls'
  | 'strip-trailing-commas' | 'unwrap-double' | 'tolerant' | 'fallback';

Options in practice

// Keep your app running on any input: return {} instead of null when unrepairable.
repairJson(raw, { fallback: 'empty-object' });

// A field whose string value just happens to look like JSON? Don't unwrap it.
repairJson(raw, { unwrapDoubleEncoded: false });

How it fixes things

It tries a fixed ladder of strategies and returns at the first one that yields valid JSON, telling you which rung fired via result.strategy:

| strategy | Fixes | |------------|-------| | 'parse' | already valid JSON — returned verbatim, never rewritten | | 'strip-fences' | ```json … ``` markdown wrappers | | 'strip-controls' | literal control chars in strings (\n/\t/\r kept as escapes, others dropped) | | 'strip-trailing-commas' | ,} / ,] | | 'unwrap-double' | a JSON string that itself encodes an object/array | | 'tolerant' | stray / missing / mismatched closers; premature root close keeping siblings; truncation | | 'fallback' | nothing worked → null or {} (your choice), never throws |

The 'tolerant' rung is the differentiator: a stack-aware parser that, when a }/] would close a container too early, decides whether it belongs to an ancestor (yield) or is just stray (skip) — and knows the root can't have a sibling, so a field after a premature root close is reclaimed instead of lost.

Guarantees

  • Never throws. Every input returns a RepairResult. Garbage → 'fallback'.
  • Never silently drops data. Siblings orphaned by a premature close are kept.
  • Never rewrites valid JSON. Valid input takes the fast path: strategy: 'parse', changed: false, value identical to JSON.parse.
  • Always re-parseable. result.json is always valid JSON.

Runtime & size

  • Universal, zero config. One artifact runs in Node, Bun, Deno, browsers, and Cloudflare / Vercel Edge. The WebAssembly is inlined into the bundle and instantiated via initSync — no fetch, no fs, no bundler/Vite/webpack setup, no postinstall.
  • ~57 KB gzipped (the wasm carries a full Rust JSON engine). Speed-tuned: on valid 1 MB input it's on par with / faster than a pure-JS repairer.
  • Browser CSP: compiling WebAssembly needs 'wasm-unsafe-eval' in the page's Content-Security-Policy (most pages don't restrict it). Node / Bun / Deno / edge are unaffected.

How it's built

This package is a thin wrapper over the same Rust core published as the safe-json-repair crate, compiled to WebAssembly — there is no second TypeScript reimplementation to drift out of sync. Both sides run the same golden test corpus.

License

MIT © LcpMarvel