Why does my JSON say invalid when it looks fine?
Almost always a trailing comma, single quotes, an unquoted key or a comment. JSON is far stricter than JavaScript object syntax, and every one of those is legal in a JavaScript file and illegal in a JSON document.
Updated 2026-08-22
JSON is not JavaScript
This is the root of most of it. JSON was derived from JavaScript object literal syntax and is deliberately a much smaller language, but the resemblance is close enough that people write the JavaScript version by habit and expect it to parse.
Four things are legal in a JavaScript file and illegal in JSON. A trailing comma after the last item of an object or array. Single quotes around strings. Property names without quotes. And comments of any kind, whether double-slash or slash-star. Every one of these appears constantly in configuration files, because editors and many parsers are lenient, and every one of them will be rejected by a strict parser.
The confusion is compounded by formats that look like JSON and are not. JSON5 permits all four of the above. JSONC — JSON with comments — is what Visual Studio Code uses for its own settings files, which is why a config that works there fails everywhere else. If a file is accepted by your editor and rejected by your build, this is usually why.
Reading the error
Parser messages are written for the parser, not for you. "Unexpected token } in JSON at position 412" tells you where the parser gave up, which is not necessarily where the mistake is. A missing closing brace near the start is only detected at the end of the file, so the reported position can be hundreds of lines from the actual problem.
The practical rule is that the reported position is an upper bound on where to look, not a location. Read backwards from it. If the message complains about an unexpected closing brace, the fault is usually an unclosed structure earlier. If it complains about an unexpected string, the fault is usually a missing comma just before it.
A position number is also much harder to use than a line and column, which is why any decent formatter converts one to the other. Counting 412 characters into a file by hand is not a reasonable ask, and the conversion is trivial — count the newlines before that offset.
The eight things that actually break it
A trailing comma before a closing brace or bracket. The single most common cause, because deleting the last item of a list leaves the comma behind.
Single quotes. JSON strings must use double quotes, always, for both keys and values.
Unquoted keys. Property names are strings and must be quoted, even when they look like valid identifiers.
Comments. There is no comment syntax in JSON at all. If a file needs annotation, the convention is a key called something like "_comment", which is a workaround rather than a feature.
An unescaped quote or backslash inside a string. A Windows path written as "C:\Users" is invalid, because a backslash begins an escape sequence — it must be doubled.
A literal newline inside a string. Strings cannot span lines; the break must be written as an escape.
NaN, Infinity or undefined. None of these are representable. A serialiser handed one usually emits null, which quietly changes your data rather than failing.
A leading zero or a trailing decimal point in a number. 007 and 1. are both rejected, though 0.5 and 1e3 are fine.
Numbers, and the bug you will not notice
JSON numbers are decimal literals with no stated size limit, but almost every parser reads them into a double-precision float. That gives exact integers only up to about nine quadrillion — 2^53. Beyond that, values are silently rounded.
This matters because large identifiers are common. A 64-bit database ID, a Twitter-style snowflake ID or a large transaction number can exceed the safe range, and the parser will not complain: it will return a number that is close and wrong, usually differing in the last digit or two. The record you fetch afterwards is then the wrong record.
The convention that avoids it is to serialise large identifiers as strings. That is why so many APIs return "id": "1234567890123456789" with quotes around a value that is obviously numeric — not an oversight, but the only way to guarantee it survives a round trip intact.
Duplicate keys, and other things that parse but should not
Not every problem is a syntax error. JSON permits duplicate keys — the specification does not forbid them and simply says the behaviour is undefined — so a document with the same property twice will parse cleanly and then behave differently depending on which parser read it. Almost all of them keep the last occurrence, but nothing guarantees that, and a config with a setting repeated in two places is a genuinely nasty bug because it looks fine.
Key order is the same kind of trap. Objects are formally unordered, so two systems may emit the same data with the properties in different sequences. That defeats diffing entirely: a comparison shows dozens of changed lines where nothing actually changed. Sorting keys before comparing is the fix, and it is why a formatter with that option is more useful than one without.
Unicode escapes are the third. A character can be written literally or as a \u escape, and both are valid and equal — so two documents that are byte-different can be semantically identical. Anything above the basic multilingual plane, which includes most emoji, is written as a surrogate pair of two escapes, so what looks like two characters is one.
Fixing a file quickly
Format it first. Indentation makes structural problems visible almost immediately — an unclosed brace shows up as a block that never returns to the left margin, and a missing comma leaves two values sitting at the same level with nothing between them.
If a large file will not parse at all, bisect it. Delete the second half and try again; whichever half still fails contains the fault. Four or five rounds of that will find a broken bracket in a ten-thousand-line document faster than reading it.
And be careful where you paste it. The files people most often need to validate are API responses carrying bearer tokens, configuration containing connection strings, and exported records containing personal data. A formatter that uploads the document to a server has taken a copy of all of that. Anything that parses in your browser cannot, which is the reason the tools here work the way they do.