Start with the original input
Run JSON.parse on the exact API response before attempting any repair. Check the HTTP status and Content-Type: an HTML error page is not JSON. Error messages and positions vary by JavaScript engine.
BOM and invisible characters
Some editors offer UTF-8 with BOM. If a decoded string begins with U+FEFF, JSON.parse rejects it; remove only that initial marker when your input contract permits it. Do not assume every editor adds a BOM.
A zero-width character outside a JSON string is not JSON whitespace. Inside a quoted key or value it can be valid data. In particular, U+200D joins Emoji sequences. Report these characters for review; never delete them globally.
Trailing commas and quoted text
A comma before a closing bracket can be a syntax error, but the text inside a quoted string must remain unchanged. Single quotes, comments and unescaped control characters require separate diagnosis, not a global replacement.
Conservative repair with a string-aware scanner
This standalone TypeScript function tracks quoted strings and escaped quotes. It removes only a leading BOM and trailing commas outside strings, and offers a candidate only if JSON.parse accepts the complete result. Review changes before using the output.
/** Conservative repair: preserve string contents; never strip invisible characters globally. */
export function repairJsonSyntax(input: string): { candidate?: string; trailingCommaFound: boolean } {
const text = input.charCodeAt(0) === 0xfeff ? input.slice(1) : input;
let output = "";
let inString = false;
let escaped = false;
let previous = "";
let trailingCommaFound = false;
for (let index = 0; index < text.length; index++) {
const char = text[index];
if (inString) {
output += char;
if (escaped) escaped = false;
else if (char === "\\") escaped = true;
else if (char === '"') { inString = false; previous = '"'; }
continue;
}
if (char === '"') inString = true;
if (char === ",") {
let next = index + 1;
while (next < text.length && /[\x20\t\r\n]/.test(text[next])) next++;
const closes = text[next] === "]" || text[next] === "}";
if (closes && previous && !"[{,:".includes(previous)) {
trailingCommaFound = true;
continue;
}
}
output += char;
if (!/[\x20\t\r\n]/.test(char)) previous = char;
}
// Only offer a repair if the complete candidate is valid JSON.
try {
JSON.parse(output);
return { candidate: output === input ? undefined : output, trailingCommaFound };
} catch {
return { trailingCommaFound };
}
}
Verify that valid data stays unchanged
Test quoted commas, escaped quotes, backslashes, nested arrays and Emoji. Valid JSON must remain byte-for-byte unchanged. Inputs such as [,] or {,} must not become empty containers. A token split by an invisible character must still fail.
const original = JSON.stringify({ text: ",}", emoji: "👩💻" });
console.assert(repairJsonSyntax(original).candidate === undefined);
console.assert(repairJsonSyntax("[,]").candidate === undefined);
console.assert(repairJsonSyntax('{"items":[1,2,],}').candidate === '{"items":[1,2]}');
Locate and review remaining errors
Use /tools/json/json-validator to inspect the original error and any available line/column. An unavailable position is not necessarily the first character. Confirm key names and values against the API contract after repair.
For HTML responses, double parsing and truncated payloads, see /articles/json-unexpected-token. For encoding and surrogate pairs, see /articles/unicode-json-escapes.
Data handling and limitations
The tool parses locally and does not intentionally upload pasted input. Minimize production samples and remove secrets before sharing. A successful JSON.parse proves syntax validity, not schema correctness, unique keys or exact handling of large integers.