The most frustrating thing about JSON parse errors is that the message tells you where parsing gave up, while the real cause is often earlier. This guide uses a fixed order, first rule out non-JSON, then syntax, then content, to locate Unexpected token style errors, and it separates what a tool can repair in one click from what you must chase back to the data source.
Step 1: confirm you actually have JSON
Before hunting character by character, look at the first and last characters. This step eliminates roughly half of the odd errors people see.
| Symptom | Real cause | Action |
|---|---|---|
| First character is < (such as <!DOCTYPE) | The server returned an HTML error, login or gateway page | Check the HTTP status and Content-Type, not the JSON |
| First character is invisible | A BOM (byte order mark) at the start | Use JSON Repair, which strips the BOM |
| Starts with jQuery123( or callback( | JSONP, not JSON | Remove the function wrapper before parsing |
| Whole value wrapped in quotes with \" inside | JSON was serialized twice into a string | Unescape with JSON Escape, then format |
| Input is empty | The request failed or the body is empty | Check the request; the formatter shows no error for empty input |
Step 2: read the error position correctly
When you paste invalid content into the JSON Formatter, you see the parser error. One detail: the page tries standard JSON first and falls back to JSON5, and when both fail the message shown comes from JSON5, in a form like "JSON5: invalid character ... at line:column". The browser console and Node.js use different wording, for example "Expected double-quoted property name in JSON at position 7". The position conventions differ but both point at the same area.
Rule of thumb: the reported position is not always the mistake itself. A missing comma is reported at the opening quote of the next key. Truncated data is reported at the end of the text. For bare words such as None, JSON5 sometimes points at the second letter. After reading an error, also inspect the few characters before it.
Step 3: work through real examples by category
1. Trailing commas and comments: auto-fixable
{"a": 1, "list": [1, 2, 3,],} // two trailing commasStandard JSON forbids trailing commas, and native parsing reports "Expected double-quoted property name". This is the most common hand-written config mistake. The JSON Repair page removes every trailing comma and leaves commas and slashes inside strings untouched. Comments are handled the same way: // and /* */ are removed.
2. Single quotes, bare keys, smart quotes: auto-fixable
{'name': 'Ada'} // single quotes
{name: "Ada"} // unquoted key
{“name”: “Ada”} // smart quotes pasted from a documentThe first two are accepted by the loose parser and written back with double quotes. Smart quotes (“ ”) usually arrive from Word or chat apps, and the repair page replaces them with straight quotes first. Note that the replacement is global: if a string value legitimately contains curly quotes, they become straight quotes, so double-check.
3. Python-style values: auto-fixable
{"ok": True, "owner": None, "debug": False}Output copied from print(dict) is not JSON: True, False and None are capitalized, and single quotes are typical. The repair page converts them to true, false and null. The more robust habit is to use json.dumps in Python instead of copying print output.
4. Missing commas: not auto-fixed, repair by hand
{"a": 1 "b": 2}Native error: "Expected , or } after property value". The JSON5 error is invalid character at the opening quote of the second key. A tool will not insert the comma because it cannot tell whether you forgot a comma or an entire key-value pair. Use the reported position, then add the comma after the previous value. In large files, jump to the reported line and column in your editor.
5. Truncated data: not auto-fixed, go back to the source
{"a": [1, 2The message is "invalid end of input" at the end of the text. Common causes include a logging system limiting line length, copying a partial selection, a request cut off by a timeout, or a database column that was too short. Manually closing the brackets just yields valid-looking JSON with missing data, which is dangerous. Fetch the complete payload again.
6. Invalid escapes: understand the fallback before trusting the result
{"path": "C:\Users\bob"}In standard JSON a backslash must be paired. \U is an illegal escape and native parsing reports "Bad escaped character". The catch: the loose JSON5 fallback accepts it, treats \U as a plain U and produces CUsers, while \b becomes a backspace control character. The formatter says success, yet the data has been quietly altered. Write backslashes in paths as \\ and inspect string values after formatting.
7. Real newlines and tabs inside strings: must be escaped
String values cannot contain raw line breaks or tab characters. The native error is "Bad control character in string literal". Multi-line text must be written with \n. Long strings copied out of logs hit this constantly.
8. undefined, NaN, Infinity: not JSON
{"a": undefined} cannot be parsed in any mode. NaN and Infinity are rejected by native JSON; the loose fallback reads them, but writing standard JSON turns them into null, so the original numeric information is gone. Handle these at the producer: replace them with null or a string before serializing, and document the meaning.
What can be auto-fixed and what cannot: one table
| Error type | JSON Repair page | Notes |
|---|---|---|
| Comments (// and /* */) | Yes | Text inside strings is kept |
| Trailing commas | Yes | Only before } or ] |
| Single quotes, bare keys | Yes | Via the loose parser, written with double quotes |
| Smart quotes, BOM | Yes | Global replacement, verify the result |
| True / False / None | Yes | Replaced by word boundary, so the same words inside strings are replaced too; verify |
| Missing comma or colon | No | Author intent cannot be inferred |
| Truncation, unclosed brackets | No | Completing would be inventing data |
| undefined | No | Not a legal value, fix at the producer |
| Schema mismatch | Not a syntax issue | Use the JSON Schema Validator for missing fields and wrong types |
Worked example: a config that looks like it has one error
This config has several problems, and you meet them one at a time:
{
// service config
'name': 'billing',
"retries": 3,
"debug": False,
"ports": [8080, 8081,],
"owner": None
}After one repair pass the comment is gone, single quotes become double quotes, False and None become false and null, and the trailing comma is removed, giving valid JSON. If you only format without repairing, False makes parsing fail on line 5, and only after fixing it does the None problem surface, the classic one-error-at-a-time experience. For text of unknown origin, repair first and format second to see the complete picture at once.
Step 4: syntax is fine but the content is still wrong
Once syntax is valid, the second most common problem is content that does not match expectations: missing fields, changed types, numbers that became strings. Stop looking at syntax and check with the JSON Schema Validator, which lists each mismatch as a path and a reason, for example $.items[0].price should be a number. For integers beyond 2 to the power of 53, JavaScript loses precision, so send IDs as strings. Many apparent syntax errors are really data problems: the same endpoint returns different shapes in different environments, for example an array in staging but null in production when there is no data. Formatting both responses and diffing them is often faster than staring at the error message.
Prevent it at the source
Fixing the same class of error repeatedly means the problem is on the producer side. These habits stop most syntax errors where the data is created.
| Scenario | Wrong approach | Right approach |
|---|---|---|
| Python emitting JSON | Copying the result of print(dict) | Use json.dumps(obj, ensure_ascii=False) to get real double quotes and true / null |
| JavaScript building JSON | String concatenation, which breaks when a value contains a quote | Use JSON.stringify and let it handle escaping |
| Hand-written config | Writing from memory with no validation | Enable JSON syntax checking in the editor and parse the file in CI before merging |
| Logging | Truncating the whole log line by length | Serialize completely first, or truncate an oversized field rather than the whole JSON |
| HTTP APIs | Returning an HTML page on error while Content-Type still says JSON | Return JSON errors with the right status code and Content-Type |
Two more habits are worth building. First, have the consumer check Content-Type and the first character before parsing, and report "received HTML" when it sees <, rather than throwing an obscure parse error. Second, when parsing fails, log a few dozen characters around the error position instead of the whole response, which helps debugging and also avoids writing sensitive data into logs wholesale.
A one-time checklist
- Does the response start with { or [, and is Content-Type JSON?
- Are there unescaped newlines, tabs or backslashes inside strings?
- Are there trailing commas, comments, single quotes or Python-style True / None?
- Are brackets closed in pairs, and is the end of the text truncated?
- Once syntax passes, do fields and types match the agreed contract?
When not to rely on online repair
- Data with sensitive information when you are unsure of page behavior: redact first, or process it locally on the command line with jq.
- Audit data that must be byte-for-byte exact: any automatic repair can change content, so re-export from the source.
- Truncated large files: closing brackets is meaningless, so find out why the upstream truncated it.
- Configs that must keep comments: repair output is standard JSON and comments are removed, so save to a new file instead of overwriting the original.