What Counts as Valid JSON

The grammar fits on a single page and the whole format has six types. That simplicity is why JSON won, and it's also why everyone who has implemented it has answered the questions it leaves open in their own way.
If your document parses in one language and fails, or worse, silently changes in another, the cause is almost always one of the ambiguities below. It's rarely a bug.
Two documents claim to define it
Douglas Crockford specified the format around 2001 and published the grammar at json.org. Formal standardisation came much later, and twice over.
ECMA-404 appeared in October 2013 and was revised in December 2017. It's remarkably short and does one thing: it states the syntax. It deliberately says nothing about what a parser must accept or how it should behave on anything unusual.
RFC 8259, also from December 2017, is the current IETF specification and carries the designation STD 90. It describes the same grammar, then adds what ECMA-404 leaves out: the application/json media type, a requirement that text exchanged between systems be encoded in UTF-8, and several paragraphs of interoperability guidance.
Neither supersedes the other. A document can satisfy the grammar both describe and still be something RFC 8259 warns you about, and most of the trouble comes from that gap.
Two earlier revisions matter historically. RFC 4627, from 2006, required the top level of a JSON text to be an object or an array. RFC 7159, in 2014, relaxed that so any value could stand alone. So 42 and "hello" are complete JSON documents under the current standard but weren't under the original one, which is why an old parser somewhere in your stack may still reject them.
Where implementations part company
Duplicate names
RFC 8259 says names within an object SHOULD be unique and notes that behaviour with duplicates is unpredictable. It doesn't forbid them. In practice JavaScript keeps the last occurrence, some Python configurations keep the last, strict parsers raise an error, and a few collect them all into a list. Nothing in the format tells any of them they're wrong.
Number range
The grammar allows arbitrarily long numbers with no stated limit. RFC 8259 notes that you get good interoperability when implementations stay within the range of an IEEE 754 double, which caps exact integers at 9007199254740991. Past that, a value that was valid on the way in comes out as a different value, and nothing raises an error at any point.
Lone surrogates
A string may contain \uD800 with no matching low surrogate. The grammar permits it and JavaScript's parser accepts it, but it doesn't correspond to any Unicode character. Anything downstream that re-encodes to UTF-8 is entitled to reject or replace it.
Nesting depth
Neither standard sets a limit, so every parser either sets its own or finds out the hard way with a stack overflow.
Nicolas Seriot documented all this systematically in 2016 in a piece titled Parsing JSON is a Minefield, along with a public test corpus of ambiguous and malformed documents. He ran it against roughly thirty parsers. No two agreed on every case, and several crashed outright.
The missing comment syntax
Comments were left out on purpose. Crockford has said he removed them because he saw people using comments to carry parsing directives, which would have split the format into dialects that only certain readers could understand.
As a result, every configuration file that wants annotations has needed a superset. JSON5 and JSONC both add comments and trailing commas; tsconfig.json and VS Code settings use the latter. They're useful formats, but they aren't JSON, and a compliant parser is right to reject them.
That's why our JSON formatter won't strip comments before parsing. If it quietly accepted a document that isn't JSON, it would teach you something false about what your own pipeline will accept.
When the container is the wrong one
JSON has no framing. A parser reads a complete document, so a 4 GB export has to be fully in memory before you can look at the first record.
NDJSON, one compact JSON value per line, fixes this without inventing anything new. Each line parses independently, so you can stream, append to and split the file with standard tools. It's the right shape for logs, exports and event streams.
For serious analytical volume, a columnar binary format such as Parquet or Arrow will beat any text encoding by an order of magnitude on both size and scan speed. What JSON has going for it is that you can open it in any editor and read it, which is exactly why formatting and diffing it is still worth doing. For the diffing part, there's JSON Compare.