What valid JSON is
RFC 8259 and ECMA-404 describe the same syntax. ECMA-404, now in its 2nd edition from December 2017, defines only that syntax and nothing about what the data means. A JSON text is one value, and a value is one of six things:
- an object:
{then zero or more"name": valuemembers separated by commas, then} - an array:
[then zero or more values separated by commas, then] - a string in double quotes
- a number: an optional minus, digits, an optional fraction, an optional exponent
- one of three literal names,
true,falseornull, which the RFC says must be lowercase
Whitespace may appear around the structural characters, and only four characters count as whitespace: space, tab, line feed and carriage return. Since RFC 8259, the top level does not have to be an object or an array; "hi" and 42 are complete JSON texts. The RFC also allows a parser to accept extensions, which is why some tools take input that others reject. A strict parser such as JSON.parse in a browser does not.
The common errors at a glance
| Error | Example | Why it fails | Fix |
|---|---|---|---|
| Trailing comma | [1, 2, 3,] | A comma must be followed by another value or member | Delete the last comma |
| Single quotes | {'a': 1} | Strings start and end with double quotes only | Use " for keys and strings |
| Unquoted key | {a: 1} | A member name is a string, so it needs quotes | Quote every key |
| Comment | // note | The grammar has no comment syntax | Remove it or move it into data |
| NaN or Infinity | {"x": NaN} | Not allowed as numbers | Use null or a string |
| Leading zero | {"n": 007} | Leading zeros are not allowed | Write 7, or "007" as a string |
| Bare decimal point | {"n": 1.} | A point needs at least one digit after it | Write 1.0 or 1 |
| Raw tab or newline in a string | "line 1⏎line 2" | U+0000 to U+001F must be escaped | Write \n, \t, \r |
| Capitalized literal | {"ok": True} | true, false and null must be lowercase | Write true, false, null |
| undefined | {"x": undefined} | Not a JSON value | Use null or drop the key |
| Byte order mark | U+FEFF before [ | Not one of the four whitespace characters | Save as UTF-8 without BOM |
The fastest check is to paste the text into the JSON Formatter, which parses it, reports the first error and pretty-prints the result once it is valid. The sections below show each error in full.
Trailing commas
In the grammar a comma is a separator: it must sit between two values or two members. A comma right before ] or } leaves the parser expecting another item that never comes. It slips into hand-edited files because JavaScript, Python and many config formats allow it.
Broken: {"name": "Ada", "langs": ["en", "fr",],}
Fixed: {"name": "Ada", "langs": ["en", "fr"]} Single quotes and unquoted keys
A string begins and ends with a double quotation mark, and an object member name is a string. So 'Ada' is not a string and name without quotes is not a key. Both come from copying a JavaScript object literal. Apostrophes inside a double-quoted string are fine and need no escape; a double quote inside one must be written \".
Broken: {name: 'Ada', 'city': 'Paris'}
Fixed: {"name": "Ada", "city": "Paris"}
Fixed: {"quote": "She said \"hi\" and didn't stop"} Comments
There is no comment syntax in JSON, neither // nor /* */. MDN lists comments among the JavaScript features JSON leaves out. If notes have to live in the file, make them data. If people edit the file often, write it in YAML, which has comments, and convert it with YAML to JSON when a program needs JSON. The guide JSON vs YAML vs CSV compares the formats.
Broken: {
"port": 8080 // dev only
}
Fixed: {
"port": 8080,
"_comment": "dev only"
} Numbers: NaN, Infinity, leading zeros
RFC 8259 states that numbers the grammar cannot represent, such as Infinity and NaN, are not permitted, and that leading zeros are not allowed. MDN adds that a decimal point must be followed by at least one digit. A plus sign is allowed only in the exponent, as in 1e+3, never in front of the number.
Broken: {"ratio": NaN, "zip": 02134, "price": 5., "delta": +2}
Fixed: {"ratio": null, "zip": "02134", "price": 5.0, "delta": 2} A ZIP code or an ID with a leading zero is really text, so store it as a string. NaN usually sneaks in through code: JSON.stringify does not fail on it but, as MDN documents, writes NaN and Infinity as null. The output is valid JSON, but the number is gone, so check for it before serializing if the value matters.
Control characters inside strings
The RFC requires the control characters U+0000 through U+001F to be escaped inside a string. That range includes tab, line feed and carriage return, so a string that spans two lines in the file, or contains a literal tab pasted from a spreadsheet, is invalid. Write the escape sequences instead: \n for a line feed, \t for a tab, \r for a carriage return, \\ for a backslash and \uXXXX for any other code point.
Broken: {"address": "1 Main St
Springfield"}
Fixed: {"address": "1 Main St\nSpringfield"} Windows paths trip on the same rule from the other side: "C:\new" is read as C, colon, line feed, e, w. Double the backslash: "C:\\new". Text Escape and Unescape converts a block of text with quotes, backslashes and line breaks into a valid JSON string in one step.
Literals, undefined and the byte order mark
True, False and None are Python spellings, not JSON. The literal names must be lowercase and there are only three, so undefined is not a value either. Use null, or leave the key out.
A byte order mark is the invisible character U+FEFF that some editors put at the start of a UTF-8 file. RFC 8259 says implementations must not add one to JSON sent over a network, and that a parser may ignore one rather than treat it as an error. JSON.parse does not ignore it: U+FEFF is not one of the four whitespace characters, so a string that starts with it fails at position 0 even though the text looks perfect. Save the file as UTF-8 without BOM, or strip a leading \uFEFF before parsing.
Reading the error position
JSON.parse throws a SyntaxError when the text is not valid JSON. The message wording depends on the browser engine. MDN's example of a trailing comma reads:
JSON.parse("[1, 2, 3, 4, ]");
// SyntaxError: Unexpected token ] in JSON at position 13 The position is a zero-based count of characters from the start of the text. In [1, 2, 3, 4, ] the closing bracket is at index 13, the 14th character, right after the comma that caused it. Firefox reports a line and a column instead, in the form at line 1 column 2 of the JSON data, which is easier to find in a long file.
The position marks where the parser gave up, which is not always where the mistake is. MDN's leading zero example, {"foo": 01}, fails with "expected ',' or '}' after property value": the parser accepted 0 as a complete number and then choked on the 1. A missing comma between two members likewise shows up at the start of the second one. When the marked spot looks correct, read the few characters just before it.
Once it parses
- Check for duplicate keys. The RFC says names within an object should be unique but does not forbid duplicates, and parsers differ on which one wins.
- Check the types.
"42"and42are both valid but not the same. JSON to TypeScript turns a sample into type definitions, which makes a string where a number should be easy to spot. - Generate JSON, do not hand-write it. Build the data as objects in code and serialize it with the language's JSON library. Every error in the table above comes from text typed or pasted by hand.
Frequently asked questions
Why does my JSON work in JavaScript but not in JSON.parse?
Because a JavaScript object literal allows things JSON does not: single quotes, unquoted keys, trailing commas, comments, undefined and NaN. MDN puts it plainly: JSON is based on JavaScript syntax, but most of JavaScript is not JSON. Paste the text into a validator and it will show the first character that breaks the stricter grammar.
Can JSON have comments?
No. The grammar in RFC 8259 has no comment syntax, and MDN lists comments among the JavaScript features JSON does not support. If a file needs notes, store them as data under a key, or write the file in YAML, which has comments, and convert it to JSON when a program needs it.
Is a trailing comma allowed in JSON?
No. After a comma the grammar expects another value in an array or another name and value pair in an object. A comma right before ] or } is a syntax error in every conforming parser, even though JavaScript, Python and many config formats accept it.
What does "at position 13" mean in a JSON error?
It is a zero-based character offset into the text, counted from the first character. Position 13 is the 14th character. In MDN's example "[1, 2, 3, 4, ]" that is the closing bracket right after the trailing comma. Other engines report a line and column instead, which is easier to use in a long file.
Is a plain string or number valid JSON on its own?
Yes. RFC 8259 defines a JSON text as a single serialized value of any type, so "hello", 42 and null are all valid documents. Older specifications required the top level to be an object or an array, which is why some old tools still reject a bare value.
Sources
- RFC 8259, The JSON Data Interchange Format (the grammar for values, objects, arrays, numbers and strings; literal names must be lowercase; leading zeros and Infinity and NaN not permitted; control characters U+0000 to U+001F must be escaped; four whitespace characters; UTF-8 and no byte order mark, parsers may ignore one; names SHOULD be unique; a JSON text is any value; parsers MAY accept extensions)
- Ecma International, ECMA-404 The JSON data interchange syntax (2nd edition, December 2017; defines only the syntax of valid JSON texts)
- MDN Web Docs: JSON.parse() (throws SyntaxError for text that is not valid JSON; the trailing comma and single quote examples with the message at position 13)
- MDN Web Docs: SyntaxError: JSON.parse: bad parsing (Firefox messages with line and column, and the leading zero and bare decimal point examples)
- MDN Web Docs: JSON (JavaScript and JSON differences: double-quoted names, no trailing commas, no leading zeros, a digit after the decimal point, no NaN, Infinity, undefined or comments; the four whitespace characters)
- MDN Web Docs: JSON.stringify() (NaN and Infinity are serialized as null; undefined is omitted in objects and becomes null in arrays)