Fix JSON SyntaxError: Unexpected Token
SyntaxError: Unexpected token is the most common JSON parse error. Learn the 8 causes, how to diagnose which one you have, and the exact fix for each.
Have broken JSON right now? Fix it free in under 1 second — no signup.
Fix My JSON →SyntaxError: Unexpected token is the most common JSON error message, and also the least helpful. It tells you something is wrong but not what. The "unexpected token" could be a rogue comma, a stray letter, a Python keyword, or an invisible character at the start of the file.
This guide walks through the 8 most common causes, shows you exactly how to diagnose which one you have, and gives the specific fix for each.
What "Unexpected Token" Actually Means
When JavaScript's JSON parser reads your input, it expects each character to be a specific, valid part of the JSON grammar. When it encounters something that doesn't fit, it throws:
SyntaxError: Unexpected token X in JSON at position N
Where X is the character it didn't expect, and N is the position in the string (0-indexed character count).
In newer Node.js versions (v20+), the error message is more descriptive:
SyntaxError: Expected property name or '}' in JSON at position 42
But the underlying cause is the same: the parser hit something that doesn't belong in valid JSON at that location.
The key diagnostic information is the position number. Open your JSON string in a text editor, count (or use the editor's "go to character" feature) to that position, and you'll find the exact location of the problem.
The 8 Most Common Causes
1. Trailing Comma
What it looks like:{
"name": "Alice",
"age": 30,
}
The error message will point at the } because the parser found } where it expected another property name (after the comma).
2. Unquoted Key
What it looks like:{name: "Alice", age: 30}
The error points at n (the first character of name) because the parser expected " (a string) or } (end of object) but got a bare letter.
3. Single-Quoted String
What it looks like:{'name': 'Alice', 'age': 30}
The error points at ' because JSON only recognizes double quotes as string delimiters.
4. undefined, NaN, Infinity
What it looks like:
{
"value": undefined,
"ratio": NaN,
"limit": Infinity
}
undefined, NaN, and Infinity are valid JavaScript values but not valid JSON values. JSON only has null, true, false, numbers, strings, objects, and arrays.
The error points at the u in undefined, the N in NaN, or the I in Infinity.
JSON.stringify — it silently converts undefined to nothing and NaN/Infinity to null. The issue appears when serializing from Python or another language that produces these as literal strings.
5. JavaScript Comment
What it looks like:{
// this is the user name
"name": "Alice",
"age": 30 / years /
}
The error points at the first / in the comment.
6. Single Quotes vs Double Quotes Inside String Values
What it looks like:{"message": "It's a "great" day"}
The inner double quotes around great break the string. The parser sees the string end at It's a and then finds great as an unquoted identifier.
The error points at g in great.
7. Extra Data After the Root Value
What it looks like:{"name": "Alice"} {"name": "Bob"}
Valid JSON has exactly one root value. Two objects separated by whitespace is invalid (even though ndjson/jsonlines uses this format, standard JSON.parse does not).
The error points at the second {.
8. Byte Order Mark (BOM)
What it looks like:The file looks completely valid to the eye, but the error says Unexpected token at position 0. The actual character is invisible: a UTF-8 BOM (\uFEFF) at the very start.
SyntaxError: Unexpected token in JSON at position 0
(The character shown is invisible or shows as a box in some terminals.)
Frequency: Less common but frustrating because it's invisible. Happens with JSON files saved from Windows text editors (especially Notepad) or exported from Excel.How to Diagnose Which Cause You Have
Step 1: Read the Position
The position in the error message is your best clue. Count to that character in your JSON string.
In Node.js, you can do this programmatically:
function diagnoseJsonError(jsonStr) {
try {
JSON.parse(jsonStr);
console.log('Valid JSON');
} catch (e) {
const match = e.message.match(/position (\d+)/);
if (match) {
const pos = parseInt(match[1]);
const context = jsonStr.slice(Math.max(0, pos - 20), pos + 20);
console.error(Error at position ${pos});
console.error(Context: ...${context}...);
console.error(Character: "${jsonStr[pos]}" (charCode ${jsonStr.charCodeAt(pos)}));
} else {
console.error(e.message);
}
}
}
diagnoseJsonError('{"name": "Alice", "age": 30,}');
// Error at position 29
// Context: ...ce", "age": 30,}...
// Character: "}" (charCode 125)
Step 2: Check the Character Code
If the character looks normal but the error position is 0 or 1, you likely have a BOM or invisible character:
const charCode = jsonStr.charCodeAt(0);
if (charCode === 0xFEFF) {
console.log('BOM detected at position 0');
}
if (charCode === 0x200B) {
console.log('Zero-width space at position 0');
}
Step 3: Use a Visual JSON Validator
Paste your JSON into AI JSONMedic. It shows exactly what's wrong before fixing it, so you can learn the pattern. The repair report names each issue found.
For offline diagnosis, jq is the fastest CLI tool:
echo '{"name": "Alice", "age": 30,}' | jq .
parse error (Expected another key-value pair or end of object)
at line 1, column 31
jq gives a column number which is easier to work with than a character offset for multi-line JSON.
Fix for Each Cause
Fix 1: Trailing Comma
// Remove commas immediately before } or ]
function removeTrailingCommas(s) {
return s.replace(/,(\s*[}\]])/g, '$1');
}
const fixed = removeTrailingCommas('{"name": "Alice", "age": 30,}');
JSON.parse(fixed); // works
See the complete guide on fixing trailing commas in JSON for more detail.
Fix 2: Unquoted Keys
// Wrap bare identifier keys in double quotes
function quoteUnquotedKeys(s) {
return s.replace(/([{,]\s)([a-zA-Z_$][a-zA-Z0-9_$])(\s*:)/g, '$1"$2"$3');
}
const fixed = quoteUnquotedKeys('{name: "Alice", age: 30}');
JSON.parse(fixed); // works
The regex matches a key that starts with a letter, underscore, or $, after a { or ,. This covers standard JavaScript identifier syntax.
Fix 3: Single Quotes to Double Quotes
This is tricky because you need to handle apostrophes inside string values. The simple approach works for most cases:
function singleToDoubleQuotes(s) {
// Replace single-quoted strings with double-quoted strings
// This handles most cases but can break on apostrophes in values
return s.replace(/'([^'\\](\\.[^'\\])*)'/g, '"$1"');
}
const fixed = singleToDoubleQuotes("{'name': 'Alice', 'role': 'admin'}");
JSON.parse(fixed); // { name: 'Alice', role: 'admin' }
For values with apostrophes, use JSON5:
// npm install json5
const JSON5 = require('json5');
const result = JSON5.parse("{'name': \"Alice's account\", 'role': 'admin'}");
In Python:
import re
import json
def single_to_double_quotes(s: str) -> str:
return re.sub(r"'([^'\\](?:\\.[^'\\])*)'", r'"\1"', s)
fixed = single_to_double_quotes("{'name': 'Alice', 'age': 30}")
data = json.loads(fixed)
Fix 4: undefined, NaN, Infinity
function fixJsOnlyValues(s) {
return s
.replace(/\bundefined\b/g, 'null')
.replace(/\bNaN\b/g, 'null')
.replace(/\bInfinity\b/g, 'null')
.replace(/\b-Infinity\b/g, 'null');
}
const broken = '{"value": undefined, "ratio": NaN, "limit": Infinity}';
const fixed = fixJsOnlyValues(broken);
JSON.parse(fixed); // { value: null, ratio: null, limit: null }
You might want to replace NaN and Infinity with 0 instead of null depending on your use case. The important thing is that null is the only valid JSON "absence of value."
In Python, this comes up when serializing Python objects that contain float('nan') or float('inf'):
import json
import math
data = {"value": float('nan'), "limit": float('inf')}
This will raise ValueError: Out of range float values are not JSON compliant
json.dumps(data)
Fix: use allow_nan=False to catch it, or handle manually
def safe_json_dumps(obj):
def fix_value(v):
if isinstance(v, float) and (math.isnan(v) or math.isinf(v)):
return None
return v
# Walk the structure and fix float values
import copy
def walk(o):
if isinstance(o, dict):
return {k: walk(v) for k, v in o.items()}
if isinstance(o, list):
return [walk(v) for v in o]
return fix_value(o)
return json.dumps(walk(obj))
print(safe_json_dumps(data)) # {"value": null, "limit": null}
Fix 5: JavaScript Comments
function stripComments(s) {
// Remove // line comments (but not URLs like https://)
// Remove / block comments /
// Remove # hash comments
let result = '';
let inString = false;
let i = 0;
while (i < s.length) {
if (s[i] === '"' && (i === 0 || s[i - 1] !== '\\')) {
inString = !inString;
result += s[i];
} else if (!inString && s[i] === '/' && s[i + 1] === '/') {
// Skip to end of line
while (i < s.length && s[i] !== '\n') i++;
} else if (!inString && s[i] === '/' && s[i + 1] === '*') {
// Skip to end of block comment
i += 2;
while (i < s.length && !(s[i] === '*' && s[i + 1] === '/')) i++;
i += 2;
} else if (!inString && s[i] === '#') {
// Skip to end of line
while (i < s.length && s[i] !== '\n') i++;
} else {
result += s[i];
}
i++;
}
return result;
}
const broken = `{
// user data
"name": "Alice",
"age": 30 / years /
}`;
JSON.parse(stripComments(broken)); // { name: 'Alice', age: 30 }
Or use the strip-json-comments npm package which handles these edge cases robustly:
// npm install strip-json-comments
const stripJsonComments = require('strip-json-comments');
const result = JSON.parse(stripJsonComments(broken));
Fix 6: Escaped Quotes Inside Strings
// If the JSON has unescaped double quotes inside a double-quoted string,
// the safest fix is to use a lenient parser
const JSON5 = require('json5');
// Or manually escape them if you know the structure
const broken = '{"message": "It\'s a "great" day"}';
// This requires knowing the structure to fix reliably
// The safest approach: use AI JSONMedic for complex string escaping cases
For this case, AI JSONMedic uses a character-level state machine that correctly identifies string boundaries and escapes mismatched quotes.
Fix 7: Extra Data After Root Value
function extractFirstJson(s) {
s = s.trim();
const start = s.search(/[{\[]/);
if (start === -1) return s;
let depth = 0;
let inString = false;
let i = start;
for (; i < s.length; i++) {
const ch = s[i];
if (ch === '"' && (i === 0 || s[i - 1] !== '\\')) {
inString = !inString;
}
if (inString) continue;
if (ch === '{' || ch === '[') depth++;
else if (ch === '}' || ch === ']') {
depth--;
if (depth === 0) {
return s.slice(start, i + 1);
}
}
}
return s.slice(start);
}
const broken = '{"name": "Alice"} {"name": "Bob"}';
const first = extractFirstJson(broken);
JSON.parse(first); // { name: 'Alice' }
If you actually need all the objects (ndjson format), parse them line by line:
function parseNdjson(raw) {
return raw
.split('\n')
.filter(line => line.trim())
.map(line => JSON.parse(line));
}
Fix 8: BOM and Invisible Characters
function stripInvisibleChars(s) {
return s
.replace(/^\uFEFF/, '') // UTF-8 BOM
.replace(/\u200B/g, '') // zero-width space
.replace(/\u200C/g, '') // zero-width non-joiner
.replace(/\u200D/g, '') // zero-width joiner
.replace(/\uFFFE/g, '') // UTF-16 BOM (reversed)
.replace(/\u0000/g, '') // null bytes
.trim();
}
const bom = '\uFEFF{"name": "Alice"}';
JSON.parse(stripInvisibleChars(bom)); // { name: 'Alice' }
In Python, the BOM fix is:
import json
def parse_with_bom_handling(raw: str) -> dict:
# Remove BOM if present
if raw.startswith('\ufeff'):
raw = raw[1:]
return json.loads(raw)
Or when reading from a file:
with open('data.json', encoding='utf-8-sig') as f:
# 'utf-8-sig' automatically strips the BOM
data = json.load(f)
Prevention Tips
Use a Linter in Your Editor
VS Code, WebStorm, and most modern editors validate JSON files in real time. If your file has a .json extension, the editor will flag errors before you run anything.
For project-level enforcement, add a .vscode/settings.json with:
{
"json.validate.enable": true
}
Use TypeScript for Config Files
TypeScript config files (.ts or .mts) support comments, trailing commas, and all JavaScript syntax. For configuration that humans edit, prefer TypeScript over JSON. For data interchange, stick with strict JSON.
Use JSON5 for Dev Configs
JSON5 (npm install json5) is a superset of JSON that allows trailing commas, comments, single quotes, and unquoted keys. Use it for config files that humans edit, and strict JSON for data exchanged between systems.
Validate Before Saving
In Node.js, add a validation step before writing JSON files:
function safeWriteJson(filePath, data) {
const jsonStr = JSON.stringify(data, null, 2);
// Verify it round-trips correctly
JSON.parse(jsonStr);
fs.writeFileSync(filePath, jsonStr, 'utf8');
}
Sanitize AI Output at the Source
If your application consumes AI-generated JSON, add a repair step at the ingestion point. See how to fix ChatGPT JSON errors for a complete pipeline pattern, or strip markdown from JSON for fence-specific handling.
Quick Reference: Error Diagnosis Table
| Error character | Likely cause | Fix |
|---|---|---|
} after a comma | Trailing comma | Remove the comma |
| Bare letter at key position | Unquoted key | Quote the key |
' at string start | Single quotes | Convert to double quotes |
u (in undefined) | JS undefined literal | Replace with null |
N (in NaN) | JS NaN literal | Replace with null |
I (in Infinity) | JS Infinity literal | Replace with null |
/ at non-URL position | JS comment | Strip the comment |
{ or [ after root ends | Extra JSON data | Extract first object only |
| Invisible char at pos 0 | BOM or zero-width space | Strip invisible chars |
FAQ
Q: The error says position 0. What's wrong?Position 0 means the very first character is wrong. This is almost always a BOM (\uFEFF) or other invisible character. Check the character code: jsonStr.charCodeAt(0). If it's 65279 (0xFEFF), that's a BOM. Strip it and retry.
"Unexpected end of input" means the JSON was cut off before it was complete. Common causes: truncated AI response, incomplete file write, network timeout mid-response. The JSON is valid up to the point where it ends - it's just missing its closing brackets or final values. See the truncation fix in how to fix ChatGPT JSON errors.
Q: Can I configure JSON.parse to be more lenient?Not directly. JSON.parse is spec-compliant and can't be configured. Your options are: fix the string before parsing, use JSON5 (lenient parser), or use AI JSONMedic which applies a full repair pipeline before parsing.
The JSON parser implementation changed between Node.js versions. In some versions, the position counts bytes; in others, it counts characters. For non-ASCII content, these differ. Use the context extraction approach (show 20 chars before and after the position) rather than relying on exact position counting.
Q: I fixed the trailing comma but now I get a different unexpected token error. Is there another issue?Yes. JSON files can have multiple errors. Fixing one reveals the next. The easiest solution is to paste the whole thing into AI JSONMedic which finds and fixes all issues in a single pass. Or run through the full list of causes in this article to check for each one.
Q: My JSON was valid yesterday and now it's invalid. What changed?Check for: encoding changes (file resaved with different encoding, adding BOM), edits that introduced trailing commas or unquoted keys, AI-generated content appended to the file, or copy-paste from a website that substituted curly quotes for straight quotes.
Conclusion
"Unexpected token" is a generic error message for a very specific problem: a character appeared where the JSON grammar didn't expect it. The 8 causes in this guide cover the vast majority of real-world cases.
The fastest path to a fix: check the position number, identify the character, match it to the table above, apply the corresponding regex fix. For complex cases or files with multiple issues, paste the JSON into AI JSONMedic for a one-click repair with a full issue report.
For related reading: fix trailing comma in JSON, strip markdown from JSON, and JSON syntax errors explained.
Still dealing with broken JSON?
Paste it in and get it fixed in under 1 second — free, no signup, no install. Works with ChatGPT, Claude, n8n, and any AI output.
Fix My JSON Free →Related Articles