The JSON.parse error points one step past the mistake: common JSON errors
Trailing commas, single quotes, comments and NaN that JSON parsers reject, big integers and duplicate keys that change silently, and how to read an error position, based on RFC 8259.
You tweak a config file, restart the service, and get this:
SyntaxError: Expected double-quoted property name in JSON at position 46 (line 4 column 1)
Line 4 holds a single closing brace. There is nothing to fix there. The real mistake is one line up: a comma left after the last item.
The position is where the parser gave up
A JSON parser reads left to right and stops at the first character the grammar doesn’t allow. The comma was fine on its own; after a comma the grammar expects another member, and it found } instead. So it reports the }.
So when you get a position, look at it and just before it. position is a zero-based character offset (strictly, UTF-16 code units, so one emoji counts as 2). Recent Node and Chrome add (line 4 column 1); other versions give only the offset, as in Unexpected token } in JSON at position 46, or quote the surrounding text instead. The wording varies by engine, so the position is the part to rely on.
Fine in JavaScript, invalid in JSON
Most errors come from writing JSON as though it were a JavaScript object literal. They look alike, but JSON’s grammar (RFC 8259, ECMA-404) is much narrower.
| You wrote | JavaScript object | JSON |
|---|---|---|
Trailing comma [1, 2,] |
Allowed | Error |
Single quotes {'a': 1} |
Allowed | Error: strings and keys take double quotes only |
Unquoted key {a: 1} |
Allowed | Error |
Comments // …, /* … */ |
Allowed | Error: the grammar has no comments |
NaN, Infinity, undefined |
Usable values | Error: not JSON values |
012, 0x1F, .5, +1 |
Some allowed | Error: no leading zeros, hex, bare decimal point or + sign |
| A literal tab or newline inside a string | Allowed in template literals | Error: write \t, \n |
The last row bites when you paste a cell from a spreadsheet into a JSON string. It looks like a space but it is a tab, and V8 says Bad control character in string literal.
A BOM (EF BB BF) at the very start of the file is another classic. Windows Notepad and some editors add one to UTF-8 files, and you can’t see it. RFC 8259 says not to add one, and allows parsers to ignore it. In practice they differ: JSON.parse fails on the first character, while Python’s json.loads helpfully says to decode with utf-8-sig.
Accepted, but changed
Worse are the cases where the parser says nothing and the data still comes out different.
Big integers. The JSON grammar puts no limit on a number’s size. But JSON.parse reads numbers as a JavaScript Number, a double, so integers above 2⁵³ are rounded to the nearest one it can hold.
JSON.parse('{"id": 9007199254740993}').id; // 9007199254740992
An order number or user ID arrives one digit off, and nothing complains. This is why many APIs send 64-bit IDs as strings; the floating-point article explains the mechanism. Read the same JSON in Python and the integer survives intact, because Python’s int has no size limit. The same bytes, two different values.
Duplicate keys. {"a": 1, "a": 2} is not a syntax error. RFC 8259 only says names SHOULD be unique and warns that behavior with duplicates is unpredictable. JavaScript and Python both keep the last value, 2; other parsers keep the first or reject the document. Merge two config files, end up with a key twice, and you can spend a long time wondering why your edit near the top is ignored.
Parsers that accept NaN. Python’s json module reads and writes NaN and Infinity by default. json.dumps(float("nan")) produces NaN, which isn’t standard JSON, and JSON.parse on the JavaScript side then fails. If Python is the sender, allow_nan=False stops it at the source.
What JSON.stringify drops. It goes the other way too. Properties holding undefined or a function disappear (inside arrays they become null), and NaN and Infinity become null wherever they are. A Date becomes an ISO string, and a BigInt throws (Do not know how to serialize a BigInt). If the object you sent and the one that arrived have different keys, check these rules first.
If you want comments
You can put comments in tsconfig.json and VS Code’s settings.json. Those files only borrow the .json extension: they are JSONC (JSON with Comments), which also tolerates trailing commas, and each tool reads them with its own parser. Feed one to JSON.parse and it fails. For hand-edited config that needs comments, use a JSONC or JSON5 parser; keep the data that programs exchange as plain JSON.
Let a tool find the spot
Paste JSON into the JSON formatter and validator and it reindents it, or, if the syntax is wrong, points to the exact line and column, even when the browser’s own error doesn’t say. Numbers are kept exactly as written, so big integers aren’t rounded, and duplicate keys get their own warning. Paste the four lines from the diagram and it reports line 4, column 1; delete the comma at the end of line 3 and it passes.
References
- RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format
- ECMA-404, The JSON Data Interchange Syntax
- ECMAScript specification (ECMA-262),
JSON.parseandJSON.stringify - Python documentation,
json— JSON encoder and decoder