JSON, demystified: formatting, validation, and the errors that trip everyone up
A working guide to reading, fixing, and trusting the JSON that flows through every API you touch.
JSON is the quiet workhorse of the modern web. Every REST API response, most configuration files, half your log lines, and the payload of nearly every webhook you'll ever debug are JSON. It's popular precisely because it's boring: a tiny grammar, no schema baked in, and a shape that maps cleanly onto the data structures of almost every programming language. But "small grammar" also means the rules are unforgiving, and a single stray comma can turn a 4,000-line response into a parser error with a cryptic offset.
This guide walks through what JSON actually is, the handful of rules that matter, the errors that cause 90% of real-world parse failures, and how to think about validating and formatting it so you spend less time squinting at a wall of text.
What JSON actually is
JSON — JavaScript Object Notation — is a text format for representing structured data using six building blocks:
- Objects: unordered key/value pairs wrapped in
{ } - Arrays: ordered lists wrapped in
[ ] - Strings: text in double quotes,
"like this" - Numbers:
42,-3.14,6.022e23 - Booleans:
trueandfalse - Null:
null
That's the whole language. Here's a document that uses every type:
{
"name": "widget",
"price": 9.99,
"inStock": true,
"tags": ["hardware", "featured"],
"dimensions": { "width": 10, "height": 4 },
"discontinued": null
}
Notice what JSON is not: it has no dates, no comments, no functions, no
trailing commas, and no undefined. Those omissions are deliberate. The format
was designed to be a minimal, language-neutral interchange layer, and every
feature it leaves out is one fewer thing two systems can disagree about.
The rules that actually bite
Most JSON errors come from a small set of rules that feel arbitrary until you've been burned by them a few times.
1. Keys must be double-quoted strings
In JavaScript you can write { name: "widget" }. In JSON you cannot — the key
must be a quoted string:
{ "name": "widget" }
This is the single most common mistake for people who copy an object literal out of their code and expect it to be valid JSON. It isn't. JavaScript object literals and JSON are different grammars that happen to look similar.
2. Only double quotes — never single
'widget' is a valid string in Python and JavaScript. In JSON it's a syntax
error. Strings are always delimited with ". If your string contains a double
quote, escape it with a backslash: "she said \"hi\"".
3. No trailing commas
This is legal in modern JavaScript and in most linters:
{ "a": 1, "b": 2, }
In strict JSON it's an error. The comma is a separator, so it may only appear between elements, never after the last one. Trailing commas are probably the number-one cause of "why won't this config load" across the entire industry.
4. No comments
JSON has no comment syntax. // and /* */ will both fail. This trips people up
constantly with config files, which is exactly why supersets like JSON5 and JSONC
(the flavor VS Code uses for its settings) exist. If you're hand-editing a config
and want comments, you're probably using one of those variants — not raw JSON.
5. Numbers are stricter than you think
JSON numbers can't have leading zeros (007 is invalid), can't be written as
hex (0xFF), and can't be NaN or Infinity. A number like .5 needs its
leading zero: 0.5. And critically, JSON doesn't distinguish integers from
floats — 1 and 1.0 are both just "number," which matters when a language on
the receiving end has to decide how to store them.
The big number problem
Here's a subtle bug that has cost real teams real money. JSON numbers are
technically unbounded, but JavaScript parses every number into a 64-bit float,
which can only represent integers exactly up to 2^53 − 1 (that's
9,007,199,254,740,991). A 64-bit database ID like 9007199254740993 will
silently round to 9007199254740992 the moment it passes through
JSON.parse in a browser.
The fix that battle-tested APIs use: serialize large integers — IDs, timestamps
in nanoseconds, monetary values — as strings. It looks slightly awkward
("id": "9007199254740993") but it's the only way to guarantee the value
survives a round trip through a JavaScript client intact.
Reading a parse error
When a parser rejects your JSON, it usually tells you a position — a line and column, or a character offset. That number is gold. The error is almost never at the reported position; it's usually the first place the parser could no longer make sense of what came before. So when you see "unexpected token at position 412," look at everything just before 412: a missing comma, an unclosed string, or an unquoted key a few characters back.
A quick mental checklist for any parse failure:
- Count your brackets and braces — does every
{have a matching}? - Scan for a trailing comma before a
}or]. - Look for a string that opened with
"but never closed. - Check that every key is double-quoted.
- Look for a stray control character (a raw newline inside a string is illegal —
it must be escaped as
\n).
Formatting: pretty vs. minified
The same JSON document can be written two ways. Pretty-printed (indented, one value per line) is for humans — it makes structure visible so you can scan a response and understand its shape. Minified (all whitespace stripped) is for machines and networks — it shaves bytes off every request.
{"name":"widget","tags":["a","b"],"price":9.99}
versus
{
"name": "widget",
"tags": ["a", "b"],
"price": 9.99
}
They're identical data. The rule of thumb: minify what you send over the wire, pretty-print what you read. A good formatter does both on demand, so you can paste a dense one-line API response and instantly see it laid out — and that round trip also validates the document, because a formatter can only re-indent JSON it was able to parse.
If you want to try it on your own payloads, the browser-based JSON Formatter does exactly this: paste, format or minify, and it points at the exact position of any syntax error — all locally, so sensitive data never leaves your machine.
A note on security and trust
Two habits worth keeping. First, never feed untrusted JSON to eval() — that's
how you turn a data format into a remote code execution vector. Use a real parser
(JSON.parse or your language's equivalent), which only ever produces data,
never executes code. Second, be wary of pasting production secrets — tokens, keys,
customer records — into random online formatters that ship your input to a server.
Prefer tools that run entirely client-side, where the parsing happens in your own
browser and nothing is transmitted.
The takeaway
JSON's whole appeal is that it's small enough to hold in your head. Master the five rules that actually cause errors — quoted keys, double quotes only, no trailing commas, no comments, strict numbers — and remember the big-integer trap, and you'll debug the vast majority of "invalid JSON" problems in seconds instead of minutes. When in doubt, run it through a formatter: valid JSON pretty-prints cleanly, and broken JSON tells you exactly where it fell apart.