UNPKG

ensure-json

Version:

A <3kB, dependency-free helper that repairs 'almost-JSON' text from LLMs and returns a valid JavaScript object—or throws JsonFixError.

58 lines (57 loc) 1.58 kB
// src/ensureJson.ts var JsonFixError = class extends Error { constructor(msg, raw) { super(msg); this.name = "JsonFixError"; this.raw = raw; } }; function ensureJson(raw, schema) { let parsed; try { parsed = JSON.parse(raw); } catch { parsed = tryRepair(raw); } if (schema) { const result = schema.safeParse(parsed); if (!result.success) { throw new JsonFixError("Schema validation failed", raw); } return result.data; } return parsed; } async function ensureJsonAsync(raw, schema) { return ensureJson(raw, schema); } function tryRepair(raw) { let text = raw; text = text.replace(/^\s*```(?:json)?|```\s*$/gim, ""); const firstBrace = Math.min( ...["{", "["].map((c) => { const i = text.indexOf(c); return i === -1 ? Infinity : i; }) ); if (firstBrace !== Infinity) text = text.slice(firstBrace); text = text.replace(/,\s*([}\]])/g, "$1"); text = text.replace(/'([^']+)':/g, (_, k) => `"${k}":`); text = text.replace(/([{,]\s*)([a-zA-Z0-9_]+)\s*:/g, '$1"$2":'); const openBraces = (text.match(/{/g) || []).length; const closeBraces = (text.match(/}/g) || []).length; if (openBraces === closeBraces + 1) text += "}"; const openBrackets = (text.match(/\[/g) || []).length; const closeBrackets = (text.match(/]/g) || []).length; if (openBrackets === closeBrackets + 1) text += "]"; try { return JSON.parse(text); } catch (err) { throw new JsonFixError("Failed to repair and parse JSON", raw); } } export { JsonFixError, ensureJson, ensureJsonAsync };