JSON looks simple until it isn't. A single missing comma or an extra trailing one can take down an API integration, and the resulting error messages aren't always helpful about where the problem actually is.
This guide covers how to format JSON so people can read it, the syntax errors that cause most parse failures, how to read a parser's error message, and the subtler problems — valid JSON that still produces wrong data. The rules come from RFC 8259, the standard that defines JSON.
Why pretty-printing matters
Minified JSON — everything on one line — is efficient to transmit but nearly impossible to debug by eye. Pretty-printing with consistent indentation turns a wall of characters into a structure you can actually scan: you can see nesting depth, spot where an array ends, and visually match opening and closing braces.
Compare the same data both ways:
{"user":{"id":1,"name":"Ana","roles":["admin","editor"]}}
{
"user": {
"id": 1,
"name": "Ana",
"roles": [
"admin",
"editor"
]
}
}
The second version is exactly what JavaScript produces with JSON.stringify(data, null, 2). The third argument sets the indentation; per MDN, numbers above 10 are treated as 10, and you can pass a string such as "\t" to indent with tabs. Whitespace between tokens is insignificant in JSON, so a parser treats the one-line and indented versions identically.
The rule of thumb: minify for production payloads, pretty-print for anything a human needs to read — config files, API responses you're debugging, or documentation examples.
It's also worth knowing how much minifying actually saves once HTTP compression is involved. In one test payload we built — 500 near-identical user records — the 2-space version was 76,392 bytes against 48,891 minified, about 56% larger. After gzip, the two were 1,590 and 1,487 bytes, a difference of about 7%. Your data will compress differently, but the lesson generally holds: compression recovers most of the cost of whitespace.
The most common JSON syntax errors
- Trailing commas.
{"a": 1, "b": 2,}is invalid JSON — the comma after the last value breaks strict parsers, even though it's allowed in JavaScript object literals. - Single quotes instead of double quotes. JSON strings must use double quotes.
{'a': 1}will fail. - Unquoted keys.
{a: 1}is valid JavaScript but not valid JSON — keys must be quoted strings. - Comments. JSON has no comment syntax at all — not
//, not/* */. If you need comments, you're looking for JSON5 or JSONC, not plain JSON. - NaN, undefined, or functions as values. JSON only supports strings, numbers, booleans, null, objects, and arrays — nothing else. RFC 8259 explicitly rules out Infinity and NaN.
- Numbers written the JavaScript way. Leading zeros (
007), a leading plus sign (+1), and hex (0x1F) are all invalid. - Raw line breaks inside strings. Control characters, including a literal newline, must be escaped — write
\n, not an actual line break in the middle of a string.
How to read a parse error
A parser reports the position where it noticed something was wrong, which is often just after the actual mistake. Paste the trailing-comma example into Node.js 22 and you get:
Expected double-quoted property name in JSON at position 16 (line 1 column 17)
Position 16 is the closing brace, not the comma. After a comma, the parser expects another key, so it complains about the brace. The practical habit: when an error points at a location, look at the character just before it. The wording varies between browsers and runtimes, but the position is always the useful part.
Valid JSON that still causes bugs
Passing validation only means the syntax is right. These problems parse without complaint and then cause trouble later:
- Backslashes in Windows paths.
"C:\users\ana"fails outright, because\ustarts a Unicode escape. Worse,"C:\new\folder"parses successfully — as a string containing a newline and a form-feed character. Backslashes must be doubled:"C:\\new\\folder". - Duplicate keys. RFC 8259 says names within an object SHOULD be unique and notes that implementations behave unpredictably when they aren't.
JSON.parse('{"a":1,"a":2}')in JavaScript silently keeps the last value,2; other parsers may raise an error or keep the first. - Large integers. JavaScript stores JSON numbers as 64-bit floating-point values, and RFC 8259 notes that only integers in the range −(253)+1 to (253)−1 are guaranteed to be handled exactly across systems. Parse
{"id": 9007199254740993}in JavaScript and you get9007199254740992. If you work with 64-bit database IDs, send them as strings. - Values that
JSON.stringifyquietly changes. Serializing from JavaScript doesn't fail on unsupported values; it rewrites them.
JSON.stringify({ a: undefined, b: () => 1, c: NaN, d: Infinity, e: new Date(0), f: [undefined] })
// '{"c":null,"d":null,"e":"1970-01-01T00:00:00.000Z","f":[null]}'
The undefined and function properties vanish, NaN and Infinity become null, and the date turns into a string that won't become a date again when parsed. A BigInt value throws an error instead.
Formatting conventions worth standardizing
- Pick one indentation per project — 2 or 4 spaces — and let a formatter enforce it so diffs only show real changes.
- Keep key order stable. Reordering keys produces noisy diffs even when nothing changed. Watch for one JavaScript quirk: integer-like keys always come first, so
JSON.stringify({b: 1, "2": 0, "1": 0})outputs{"1":0,"2":0,"b":1}. - Use UTF-8. RFC 8259 requires UTF-8 for JSON exchanged between systems and says a byte order mark must not be added.
- Name keys consistently. Mixing
userId,user_id, andUserIDin one API is legal JSON and a steady source of bugs.
A habit worth building
Before sending JSON into a system that will silently accept or reject it, run it through a validator that gives you a clear parse error with a location — not just "invalid JSON" with no other context. Catching a typo before it ships saves the far more annoying debugging session of tracing a 400 error back to a missing bracket three services downstream.
A short pre-flight checklist:
- Validate the payload and fix errors by looking just before the reported position.
- Check for Windows paths, regex patterns, or anything else containing backslashes.
- Confirm large numeric IDs are strings.
- Pretty-print to review, then minify (or rely on gzip) for transport.
When a payload changes and you can't see why, format both versions the same way and compare them with the Text Diff tool. It compares line by line, so a minified payload shows up as one changed line, while pretty-printed versions make the real change jump out. And if the JSON you're inspecting arrived inside a login token, the JWT Decoder will extract and format the header and payload for you.
Paste JSON to validate, pretty-print, or minify it instantly — errors show exactly what failed.
Open JSON Formatter →If your workflow also involves passing data through URLs, our URL Encoder handles the percent-encoding that JSON payloads often need when embedded in query strings.
Frequently asked questions
Can JSON files contain comments?
No. Standard JSON has no comment syntax, so a strict parser rejects both // and /* */ comments. Formats such as JSONC and JSON5 allow comments and are common in config files, but they need a parser that supports them — or the comments must be stripped before the file is read as plain JSON.
Should I indent JSON with 2 spaces or 4?
Either is fine. Whitespace between tokens is insignificant in JSON, so parsers treat both identically. Pick one per project and let a formatter enforce it; 2 spaces keeps deeply nested data from drifting too far to the right.
Why did a large ID number change after I parsed my JSON?
JavaScript parses JSON numbers as 64-bit floating-point values, which can only represent integers exactly up to 9,007,199,254,740,991. Larger values are rounded, so 9007199254740993 becomes 9007199254740992. Sending large IDs as strings avoids the problem.