Almost every JSON file that fails to parse fails for one of two reasons: someone left a comment in it, or someone left a comma after the last item. Both are legal in JavaScript, both are rejected by JSON, and neither omission is an oversight.
Comments were taken out on purpose. They had been used to carry parsing directives — instructions to the reader hidden in what was supposed to be pure data — and removing them removed the temptation. The trailing comma went for a simpler reason: the grammar is a separator list, and a separator with nothing after it is not one.
What else is not valid JSON?
More than people expect, because JavaScript accepts all of it.
| Written | Valid JSON |
|---|---|
{"a": 1,} |
no — trailing comma |
{'a': 1} |
no — single quotes |
{a: 1} |
no — unquoted key |
{"a": 0x10} |
no — hex literal |
{"a": NaN} |
no — not a JSON value |
{"a": 01} |
no — leading zero |
{"a": .5} |
no — needs a leading digit |
Keys must be double-quoted strings, every time. There is no undefined, no Infinity and no date type — a date in JSON is a string that both ends have agreed to interpret, which is why the interchange format everyone uses is the one from ISO 8601.
A validator that reports the line and the reason saves the guessing. Most parse failures are a single character, and it is rarely the character the error points at.
Does key order mean anything?
Not to the specification. An object is an unordered collection, so two files differing only in key order carry identical data and any parser is free to hand them back in any order.
In practice every mainstream implementation preserves insertion order, which is why sorting keys is useful anyway. Two exports of the same data with keys in different orders produce a diff full of noise; sort both and the diff shows only what changed.
Duplicate keys are the genuinely undefined case. The specification declines to say what should happen, and most parsers keep the last occurrence silently — so a file with a repeated key parses cleanly and loses data, which is the worst combination available.
Why do large numbers change?
Because JSON has exactly one number type and no stated precision. It does not distinguish integers from decimals, and it sets no limit on magnitude — but the receiving language does.
Read into JavaScript, a number is a double, which represents integers exactly only up to 2^53 − 1. An identifier longer than sixteen digits comes back subtly wrong: the last digits change, nothing errors, and the record now points somewhere else.
The fix is to transmit large identifiers as strings. It looks redundant and it is the difference between an ID that survives the round trip and one that does not.
Empty values are the related decision. A field holding null, an empty string, an empty array and an empty object are four different things to a parser and usually the same thing to a person, so stripping them shrinks an export considerably — and destroys the distinction between a field that was cleared and one that was never set.
What is flattening for?
Getting nested data into something flat enough for a spreadsheet, a form or a set of environment variables. A nested object becomes one level of dotted keys, so a structure two levels deep turns into keys like user.address.city.
The ambiguity is dots in the original keys. A key that already contains a dot is indistinguishable from a level of nesting once flattened, so unflattening it produces the wrong shape — which is why the operation is safe to reverse only when the original keys were plain.
Arrays add the second decision: index them numerically, or join them. Neither is wrong and the two do not round-trip the same way, so a flatten and unflatten pair is worth testing on your own data rather than assumed.
When is one object per line better?
When the file is bigger than memory. JSON Lines puts one complete object on each line with no enclosing array and no separating commas, so a reader can process a line and discard it rather than holding the whole document.
That makes it the natural format for logs, exports and anything appended to over time — a new record is a new line, with no need to reopen a bracket at the end of the file.
Converting to a proper array is a wrapping operation rather than a rewrite, which is why it is fast and why the reverse is equally cheap.
Questions people ask
What about JSON5 and JSONC? Both add comments and trailing commas, and both are separate formats. A parser expecting JSON will reject them.
Does minifying change the data? No. Whitespace between tokens is the only redundancy JSON has, so removing it is lossless.
Can a JSON file hold a single number? Yes. Any value is a valid document, so 42 on its own is legal JSON.
How do I query without writing code? A path expression addresses one branch of the tree, which is enough to check whether a field exists and what it holds.
Parse it, then work on it. The JSON formatter and validator find the failing character, the minifier reverses the indentation, sort keys and remove empty values make two exports comparable, flatten and unflatten move between shapes, the path tester checks a query, and JSON Lines to array wraps a stream into a document.