Why is my JSON invalid?
Common JSON Errors and How to Fix Them
JSON is invalid when it breaks one of the few rules in RFC 8259: strings and keys use double quotes, items are separated by exactly one comma with none after the last, the only literals are true, false and null, numbers are plain decimal, comments do not exist, and the text is a single value with nothing after it. Nearly every "invalid JSON" error you will meet is one of about a dozen violations of those rules, usually introduced by hand editing, by copying a JavaScript or Python literal, or by a transport that cut the payload short. This guide lists each one, shows what the major parsers say about it, and gives the fix.
What the grammar actually allows
RFC 8259 (the current IETF standard, identical in grammar to ECMA-404) is short enough to hold in your head:
- A JSON text is one value, surrounded by optional whitespace. Since RFC 7159 that value can be any type, so
"hello"and42are complete JSON texts; older parsers that follow RFC 4627 still insist on an object or array at the top. - Values are objects, arrays, strings, numbers,
true,falseornull. Nothing else: noundefined, noNaN, noInfinity, no dates, no functions. - Strings are wrapped in
". Inside them,"and\must be escaped, and so must every control character from U+0000 to U+001F. The only escapes are\" \\ \/ \b \f \n \r \tand\uXXXX. - Numbers are an optional
-, an integer part with no leading zeros, an optional fraction and an optional exponent.+1,01,.5,1.and0x1Fare all invalid. - Whitespace between tokens is limited to four characters: space, tab, line feed and carriage return. A non-breaking space is not whitespace to a JSON parser.
Anything that relaxes these rules is a different format. JSONC (the dialect used by tsconfig.json and editor settings files) adds comments; JSON5 adds comments, trailing commas, single quotes, unquoted keys, hex numbers and Infinity. Both are useful for configuration files. Neither is JSON, and a strict parser will reject them.
How parsers report the same mistake
Error messages differ enough between runtimes that the same mistake can look like three different problems. The table below was produced by feeding identical inputs to JSON.parse in Node 22 (V8), json.loads in Python 3, and json.Unmarshal in Go.
| Mistake | JavaScript (V8) | Python json |
Go encoding/json |
|---|---|---|---|
Trailing comma {"a": 1,} |
Expected double-quoted property name | Expecting property name enclosed in double quotes | invalid character '}' looking for beginning of object key string |
Single quotes {'a': 1} |
Expected property name or '}' | Expecting property name enclosed in double quotes | invalid character ''' looking for beginning of object key string |
Unquoted key {a: 1} |
Expected property name or '}' | Expecting property name enclosed in double quotes | invalid character 'a' looking for beginning of object key string |
Comment // x |
Expected ',' or '}' after property value | Expecting ',' delimiter | invalid character '/' after object key:value pair |
Missing comma 1 "b" |
Expected ',' or '}' after property value | Expecting ',' delimiter | invalid character '"' after object key:value pair |
NaN |
Unexpected token 'N' | accepted | invalid character 'N' looking for beginning of value |
Leading zero 01 |
Unexpected number | Expecting ',' delimiter | invalid character '1' after object key:value pair |
| Raw line break in a string | Bad control character in string literal | Invalid control character at | invalid character '\n' in string literal |
Truncated [1, 2 |
Expected ',' or ']' after array element | Expecting ',' delimiter | unexpected end of JSON input |
| Byte-order mark | Unexpected token followed by the invisible U+FEFF | Unexpected UTF-8 BOM (decode using utf-8-sig) | invalid character 'ï' looking for beginning of value |
| Two documents | Unexpected non-whitespace character after JSON | Extra data | invalid character '{' after top-level value |
Python True |
Unexpected token 'T' | Expecting value | invalid character 'T' looking for beginning of value |
Bad escape "C:\dir" |
Bad escaped character | Invalid \escape | invalid character 'd' in string escape code |
Two patterns are worth memorising. "Expecting property name" almost always means a trailing comma or a single-quoted or unquoted key. "Expecting ',' delimiter" means the parser finished a value and found something other than a comma or a closing bracket: a missing comma, a comment, or a number with a leading zero. Note also that Python accepts NaN and Infinity by default, and json.dumps will happily write them, which is how invalid JSON escapes from Python services into systems that parse strictly.
The common errors, one by one
Trailing commas
The most frequent error by a wide margin, because JavaScript, Python, Go, Rust and most modern languages allow a comma after the last element and editors encourage it. JSON does not. Paste this into the JSON Validator:
{
"name": "formattr",
"version": "1.0.0",
"keywords": ["json", "format",],
}It reports the first problem it finds, with the position of the token the parser choked on rather than the comma itself:
Unexpected `]` at line 4, column 33.
Likely cause: trailing comma after the last item on line 4.Remove the comma and validate again; the second trailing comma, after the array, is reported next. If the file is configuration you control and your loader accepts JSONC, the Allow comments & trailing commas option in the JSON Formatter will parse it leniently and write out strict JSON.
Single quotes and unquoted keys
{name: 'formattr'} is a JavaScript object literal, not JSON. This happens when someone copies from source code, a browser console or documentation. Every key and every string needs double quotes. A mechanical find-and-replace of ' with " breaks strings that contain apostrophes, so repair these by parsing the text as JavaScript (or JSON5) and re-serialising, or fix them by hand.
Smart quotes
Word processors, chat apps and some wikis replace " with typographic quotes (U+201C and U+201D). They look almost identical in a proportional font. The validator names them explicitly; any other parser will report an unexpected character at the first “.
Comments
JSON has no comment syntax. Douglas Crockford removed it deliberately so that comments could not be used to carry parsing directives. If you need annotated configuration, use JSONC with a parser that supports it, YAML, or TOML, or put documentation in a "$comment" field if your schema allows extra properties.
Missing commas
Usually the result of adding a line to the middle of an object and forgetting the comma on the line above. The parser reports the start of the new line, not the end of the old one, so look one line up from the reported position.
Non-JSON literals: NaN, Infinity, undefined, True, None
JSON.stringify quietly turns NaN and Infinity into null and drops undefined properties, so JavaScript rarely produces these. Python's json.dumps writes NaN and Infinity unless you pass allow_nan=False, and a repr() of a dict ({'ok': True, 'v': None}) is Python syntax that fails in three places. Replace them with null, a number, or a string your consumer understands.
Numbers with leading zeros, + signs or hex
Postal codes, phone numbers and IDs are the usual cause: "zip": 07001 is invalid, and even where a lenient parser accepts it, the leading zero is gone. The validator gives this case its own message, "Number with a leading zero", with a hint to write the value as a string if the zero matters, and a hex value such as 0x1F gets the hint "Hexadecimal numbers are not valid JSON." Anything that is an identifier rather than a quantity belongs in a string.
Unescaped control characters and bad escapes
A literal tab or line break inside a string is an error; it must be written \t or \n. The validator reports a raw line break as "Unescaped line break inside a string" at the position where the string opens, and its automatic fix escapes it. Windows paths are the classic bad-escape case: "C:\dir" contains \d, which is not a JSON escape. Write "C:\\dir" or use forward slashes.
A byte-order mark at the start
Notepad before Windows 10 1903 and Windows PowerShell 5.1's -Encoding UTF8 prefix UTF-8 files with U+FEFF. RFC 8259 §8.1 says implementations must not add a BOM to networked JSON and that parsers may ignore one; in practice JSON.parse, Go and Python's json.loads on a decoded string all reject it. Strip it with the Text Cleaner or re-save the file as UTF-8 without BOM. The invisible characters guide covers the BOM and its relatives in detail.
Truncated input
If the error is at the very last character and says "end of input", the payload was cut off: a log line limit, a terminal scrollback buffer, a LIMIT on a text column, or a response read before it finished. The validator reports it as "Input ends before the array closes — was it cut off?" Fixing the syntax will not recover the missing data; go back to the source.
Several documents pasted together
{"a":1}{"b":2} or one object per line is not one JSON text. The validator points at the start of the second value and says there is extra content after the end of the first, asking whether several documents were pasted together. Newline-delimited JSON (NDJSON, also called JSON Lines) is a real format used by log pipelines and bulk APIs, but it must be parsed line by line. Wrap the objects in [ and ] with commas between them if you need a single document.
Valid JSON that still causes bugs
Some problems pass validation and surface later.
| Input | Valid? | What goes wrong |
|---|---|---|
{"id": 1, "id": 2} |
Yes, per the grammar | RFC 8259 says names should be unique. JSON.parse and Python keep the last value; other parsers keep the first or reject the text. The validator accepts it with a notice: "Duplicate key: "id" (line 1). Most parsers keep only the last value." |
{"id": 12345678901234567890} |
Yes | Beyond 2^53 − 1, JavaScript and any parser that maps numbers to IEEE 754 doubles rounds it to 12345678901234567000. Send large IDs as strings. |
{"price": 0.1} |
Yes | Stored as a binary float in most languages. Use integer minor units or a decimal string for money. |
{"a": followed by a non-breaking space and 1} |
No | Looks correct on screen. U+00A0 is not JSON whitespace; the validator names it: "Unexpected invisible character (U+00A0)". |
| An empty string | No | A JSON text must contain a value. Empty HTTP bodies are the common source. |
The JSON Formatter preserves duplicate keys and the exact digits of large numbers when it pretty-prints, and adds a notice for each duplicate key, which makes both problems visible instead of silently "fixing" them.
A quick diagnostic routine
- Paste the text into the JSON Validator and read the line and column. The reported token is where the parser gave up; the mistake is at that position or just before it.
- If the hint mentions a trailing comma, comments or quotes, fix those first. They account for most real-world failures.
- If the error is at the end of the input, check whether the payload was truncated before editing anything.
- If the error points at something that looks correct, suspect an invisible character and run the text through the Text Cleaner.
- Once it validates, format it so the structure is readable, then, if the data is going into configuration, consider whether YAML would serve the humans editing it better.