How to Fix Common JSON Errors: Trailing Commas, Single Quotes, Comments and LLM Output
JSON looks like JavaScript, which is exactly why it breaks so often. The format defined in RFC 8259 is much stricter than the object literals you write in code: no trailing commas, no comments, no single quotes, no undefined, no NaN. A file that a JavaScript engine would happily evaluate as an object can still be rejected by every JSON parser on the planet.
This guide walks through the errors that account for almost every "Unexpected token" message you will see, shows what each one looks like in the parser output, and gives a fix you can apply by hand or in code. The last section deals with a newer source of broken JSON: text generated by large language models.
Read the error message before touching the file
Parsers report the position where they gave up, not where the mistake is. A missing closing quote on line 3 can surface as an error on line 40, because the parser keeps reading the rest of the file as part of one long string. Still, the position narrows the search, and the token named in the message tells you which category of problem you have.
Different runtimes word the same failure differently. Learn the phrasing of the ones you use so you can classify the error at a glance.
- Node.js and Chrome: "Unexpected token '}'" or "Expected double-quoted property name in JSON at position 42". A brace or bracket right after a comma almost always means a trailing comma.
- Firefox: "JSON.parse: expected double-quoted property name at line 3 column 1". Same causes, with line and column instead of a character offset.
- Python json module: "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)" points to single quotes or a trailing comma; "Expecting value" usually means an empty input, NaN, or a stray character.
- "Unexpected end of JSON input" or "Unterminated string" means the text was cut off: a truncated HTTP response, a log line that hit a length limit, or a model that ran out of output tokens.
- "Unexpected token < in JSON at position 0" means you received HTML, typically an error page or a login redirect, not JSON at all. Fix the request, not the parser.
Trailing commas and single quotes
These two cause more broken JSON than everything else combined, because both are legal in JavaScript, TypeScript, and Python source code. A trailing comma after the last item of an object or array is not allowed in JSON. Strings and property names must use double quotes; single quotes are invalid in both positions.
A frequent Python-specific version of this bug is writing str(my_dict) or print(my_dict) to a file instead of json.dumps(my_dict). The Python repr uses single quotes, True, False, and None, none of which are valid JSON. If you see True or None in a "JSON" file, someone serialised a dict with repr. The fix is in the code that wrote the file, not in the file.
// Invalid: trailing comma, single quotes, unquoted key
{
'name': 'Asha',
role: "admin",
"tags": ["a", "b",],
}
// Valid
{
"name": "Asha",
"role": "admin",
"tags": ["a", "b"]
}Unescaped characters inside strings
Inside a JSON string, the double quote and the backslash must be escaped, and so must every control character from U+0000 to U+001F. That last rule is the one that bites: a literal tab or newline pasted into a string value makes the document invalid, even though it looks fine in an editor. Use \t and \n instead.
Windows paths are the classic case. "C:\Users\new" contains \U, which is not a valid escape, and \n, which silently becomes a newline if the parser accepts it. Every backslash in a path must be doubled. Regular expressions stored in JSON have the same problem, so a regex like \d+ has to be written as "\\d+" in the JSON file.
Only these escapes are valid: \" \\ \/ \b \f \n \r \t and \uXXXX with exactly four hex digits. Anything else, such as \x41 or \', is an error. Characters outside the Basic Multilingual Plane, like most emoji, can be written directly in UTF-8 or as a surrogate pair of two \u escapes.
// Invalid: raw newline in the value, bad escapes in the path
{ "note": "line one
line two", "path": "C:\Users\new" }
// Valid
{ "note": "line one\nline two", "path": "C:\\Users\\new" }Comments, NaN, and other things JSON does not have
JSON has no comments. Neither // nor /* */ is allowed. Many config files that look like JSON are actually JSONC (JSON with Comments, used by VS Code settings and tsconfig.json) or JSON5, and those files are read by tolerant parsers. Copy one into a strict parser and it fails. If you control the format and need comments, use JSONC or JSON5 explicitly and parse with a library that supports it; if you must emit strict JSON, strip comments in a build step or move the explanation into a "_comment" field.
The value rules are just as strict. Watch for these in data exported from scripts and spreadsheets:
- NaN, Infinity, and -Infinity are not valid. JSON.stringify converts them to null; Python json.dumps writes NaN by default unless you pass allow_nan=False, which produces output that other parsers reject.
- undefined is not a value. JSON.stringify drops object properties set to undefined and turns undefined array items into null.
- Numbers cannot have leading zeros (007), a leading plus sign, a trailing decimal point (5.), or hex notation (0x1F). Store IDs and phone numbers with leading zeros as strings.
- Integers above 2^53 - 1 (9007199254740991) lose precision when parsed into a JavaScript number. Large database IDs and some Twitter-style snowflake IDs should travel as strings.
- A UTF-8 byte order mark at the start of a file trips some parsers. Save as UTF-8 without BOM.
- Duplicate keys are technically permitted by the grammar but behaviour is undefined; most parsers keep the last value without warning.
Fixing JSON returned by an LLM
Model output adds a new set of failure modes on top of the usual ones. The model may wrap the JSON in a markdown code fence, add a sentence before or after it, use curly "smart" quotes, include comments explaining fields, or stop mid-object when it hits its token limit. Each needs a different response.
Prevention beats repair. If the API you use supports structured output or a JSON schema mode, turn it on; it constrains generation so the result parses. Without that, ask for JSON only, give a short example of the exact shape, and keep max output tokens comfortably above the expected size so the response is not truncated.
When repair is needed, apply it in order: extract the outermost object or array, normalise quotes, strip comments and trailing commas, then parse. If the text ends abruptly, do not try to guess the missing data. Treat truncation as a failed call and retry with a higher token limit or a smaller request. After parsing, validate against a schema, because syntactically valid JSON can still have the wrong field names or types.
// Minimal extraction step before JSON.parse (JavaScript)
function extractJson(text) {
const start = text.search(/[[{]/);
const end = Math.max(text.lastIndexOf('}'), text.lastIndexOf(']'));
if (start === -1 || end < start) throw new Error('No JSON found');
return text
.slice(start, end + 1)
.replace(/[\u201C\u201D]/g, '"') // smart double quotes
.replace(/,\s*([}\]])/g, '$1'); // trailing commas
}
const data = JSON.parse(extractJson(modelResponse));A quick workflow for any broken JSON
For a one-off file, paste it into the Neemtools JSON Validator to get the exact line and column of the first error, then fix and re-validate until it passes. When the input is messy (copied from logs, a chat window, or a model response) the JSON Repair tool handles quotes, commas, and comments in one pass; review the diff it produces, because automatic repair can only guess intent. Once it parses, run it through the formatter so the structure is readable, or the minifier if it is going into a request body.
For recurring problems, fix the producer. Serialise with JSON.stringify or json.dumps rather than string concatenation, set allow_nan=False in Python so bad numbers fail loudly, and add a schema check in CI for any JSON file that humans edit by hand.