JSON.parse is one of the least forgiving functions in JavaScript. The error messages are terse, the positions are byte offsets, and the fix is often non-obvious. Here are the 7 errors you'll encounter most often, what each one actually means, and how to fix it.
How to Read a JSON Parse Error
Before the list, a quick note on reading these errors. A typical message looks like:
SyntaxError: Unexpected token '}', ..."ue": true }}" is not valid JSON
or:
SyntaxError: Expected ',' or '}' after property value in JSON at position 42
The position number is the character offset from the start of the string. Use it to pinpoint the problem:
function debugJsonError(str: string, position: number): void {
const start = Math.max(0, position - 20);
const end = Math.min(str.length, position + 20);
console.log(str.slice(start, end));
console.log(" ".repeat(position - start) + "^");
}
Now the 7 errors:
1. Unexpected Token (Trailing Comma)
Error:
SyntaxError: Unexpected token '}' at position 47
Cause: A comma after the last property or array element.
{
"name": "Alice",
"age": 30,
}
Fix: Remove the trailing comma. If the JSON comes from an LLM or config file, use jsonrepair to auto-fix.
import { jsonrepair } from 'jsonrepair';
const fixed = JSON.parse(jsonrepair(brokenJson));
Or use the Fix LLM JSON tool in your browser.
2. Unexpected Token (Single Quotes)
Error:
SyntaxError: Unexpected token ''' at position 1
Cause: Single-quoted strings. JSON mandates double quotes for both keys and string values.
{'name': 'Alice'} // invalid
{"name": "Alice"} // valid
Fix: Replace single quotes with double quotes. Simple str.replace(/'/g, '"') will break on strings containing apostrophes — use jsonrepair instead.
3. Unexpected Token (Unquoted Keys)
Error:
SyntaxError: Unexpected token 'n' at position 1
Cause: Object keys without double quotes. This is valid JavaScript object syntax but not JSON.
{name: "Alice"} // invalid — key not quoted
{"name": "Alice"} // valid
Fix: Wrap keys in double quotes, or use jsonrepair which handles this automatically.
4. Unexpected End of JSON Input
Error:
SyntaxError: Unexpected end of JSON input
Cause: The JSON string ends before the structure is complete. The input is truncated — a closing }, ], or a closing quote is missing.
{"name": "Alice", "tags": ["admin"
This often happens when:
- An LLM hits its
max_tokenslimit mid-response - A file was partially written and then read
- A network response was truncated
- You forgot to capture the full output
Fix: For LLM truncation, increase max_tokens. For partial content, jsonrepair will close the unmatched brackets and produce a structurally valid (though incomplete) result. For network truncation, retry the request.
try {
return JSON.parse(input);
} catch (e) {
if (e.message.includes('Unexpected end')) {
return JSON.parse(jsonrepair(input));
}
throw e;
}
5. Unexpected Token at Position 0
Error:
SyntaxError: Unexpected token '<' at position 0
or:
SyntaxError: Unexpected token 'B' at position 0
Cause: The input isn't JSON at all. Common culprits:
<at position 0 → you received an HTML error page (often a 404 or 500 from an API)Bat position 0 → BOM (Byte Order Mark) prepended to a UTF-8 file- Any letter → an unquoted string, Python literal (
True), or prose text
// Receiving HTML instead of JSON:
const text = await response.text();
if (text.startsWith('<')) {
throw new Error(`API returned HTML. Status: ${response.status}`);
}
const data = JSON.parse(text);
Fix: Check response.status and response.headers.get('content-type') before parsing. For BOM issues, strip the BOM: input.replace(/^/, '').
6. Unexpected Non-Whitespace Character
Error:
SyntaxError: Unexpected non-whitespace character after JSON at position 42
Cause: Valid JSON followed by extra content. This often means:
- Multiple JSON objects concatenated without an array wrapper
- JSON followed by a log line or explanation
- NDJSON/JSONL being parsed as a single JSON value
{"id": 1}{"id": 2} // two objects, no separator
{"result": true} // valid JSON
// followed by this comment
Fix: If you have concatenated objects, use JSONL parsing (split on newlines). If there's trailing content from an LLM, trim it:
// Extract just the JSON object from mixed content
function extractFirstJson(str: string): string {
const start = str.search(/[[{]/);
if (start === -1) throw new Error('No JSON found');
// jsonrepair handles the rest
return jsonrepair(str.slice(start));
}
Or use the Extract JSON from Markdown tool which handles this automatically.
7. Circular Reference / Invalid Value
Error (during stringify, not parse):
TypeError: Converting circular structure to JSON
Error (during parse):
SyntaxError: Unexpected token 'u' at position 12
(The u is from undefined)
Cause (circular reference): An object that references itself — common when working with DOM nodes, Axios error objects, or ORM entities with back-references.
Cause (undefined): JSON.stringify omits undefined values in objects but serializes them as null in arrays. If you pass undefined directly as the value, you get the string "undefined" which then fails to parse.
// This produces "undefined" string, not valid JSON:
JSON.stringify(undefined) // → undefined (not a string at all)
// This omits the key:
JSON.stringify({ a: undefined }) // → '{}'
// This produces null:
JSON.stringify([undefined]) // → '[null]'
Fix for circular references:
// Option 1: Use a replacer function
const seen = new WeakSet();
JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
});
// Option 2: Use the serialize-javascript or flatted package
import { stringify, parse } from 'flatted';
Fix for undefined values: Convert undefined to null or omit the field:
const clean = JSON.parse(JSON.stringify(obj, (_, v) =>
v === undefined ? null : v
));
Quick Reference
| Error message | Cause | Quick fix |
|---|---|---|
| Unexpected token '}' | Trailing comma | jsonrepair |
| Unexpected token ''' | Single quotes | jsonrepair |
| Unexpected token 'n' | Unquoted key | jsonrepair |
| Unexpected end of JSON input | Truncated string | jsonrepair / increase max_tokens |
| Unexpected token '<' | HTML response | Check response.status |
| Unexpected non-whitespace | Extra content after JSON | Extract first JSON object |
| Converting circular structure | Circular reference | Custom replacer / flatted |
The Defensive Parsing Pattern
For any JSON that comes from an external source (API, LLM, user input, file), use this pattern:
import { jsonrepair } from 'jsonrepair';
import { z } from 'zod';
function safeParseJson<T>(
raw: string,
schema: z.ZodSchema<T>,
options: { repair?: boolean } = {}
): T {
let parsed: unknown;
try {
parsed = JSON.parse(raw.trim());
} catch {
if (!options.repair) throw new Error('Invalid JSON');
try {
parsed = JSON.parse(jsonrepair(raw.trim()));
} catch {
throw new Error('Could not repair JSON');
}
}
return schema.parse(parsed);
}
// Usage:
const UserSchema = z.object({
id: z.number(),
name: z.string(),
role: z.string(),
});
const user = safeParseJson(apiResponse, UserSchema, { repair: true });
This pattern catches syntax errors, attempts repair, then validates the schema. Any data that makes it through is guaranteed to match your types at runtime — not just at compile time.
Tools mentioned in this post:
- JSON Formatter & Validator — see your JSON errors with highlighted positions
- Fix LLM JSON — repair broken JSON in the browser
- JSON to Zod Schema — generate runtime validators from a sample