I've code-reviewed hundreds of pull requests that include JSON configuration files, API responses, or fixture data. The number of times I've seen technically invalid JSON that "works" because the parser is lenient is staggering. Here are the seven mistakes I see most often, why they're wrong according to the spec, and how to fix them.
1. Trailing commas
This is by far the most common offender.
{
"name": "Michael",
"age": 30,
}
That trailing comma after 30 is invalid JSON. JavaScript objects allow it. Python dicts allow it. JSON does not. The spec (RFC 8259) explicitly requires that the comma only appear between values, not after the last one.
Every major browser's JSON.parse() will throw a SyntaxError on this. The reason you might not notice is that many config file parsers — VS Code's settings.json parser, for example — use a superset called JSON5 or JSONC that tolerates trailing commas. But if you're sending this over an API or feeding it into a strict parser, it breaks.
2. Single quotes
{
'name': 'Michael'
}
JSON requires double quotes. Both keys and string values must be wrapped in double quotes, never single quotes. This trips up Python developers especially, because Python's json.dumps() outputs double quotes but developers hand-writing JSON often reach for singles out of habit.
3. Unquoted keys
{
name: "Michael"
}
In JavaScript, object keys don't need quotes unless they contain special characters. In JSON, every key must be a double-quoted string. No exceptions. This is the mistake that makes the difference between a JavaScript object literal and JSON most obvious. They look similar but they are different formats with different rules.
4. Comments
{
"name": "Michael", // this is my name
"debug": true /* enable debug mode */
}
JSON does not support comments. Not single-line, not multi-line, not at all. Douglas Crockford, who created JSON, deliberately excluded comments because he saw people using them to embed parsing directives, which defeated the purpose of a simple data interchange format.
This is the single biggest pain point with using JSON for configuration files, and it's why formats like JSONC (JSON with Comments), YAML, and TOML exist. TypeScript's tsconfig.json is actually JSONC. Package.json is strict JSON. Know which one you're dealing with.
5. Undefined and NaN
{
"value": undefined,
"ratio": NaN
}
JSON supports exactly these value types: strings, numbers, booleans (true/false), null, arrays, and objects. That's it. undefined is a JavaScript concept that doesn't exist in JSON. NaN and Infinity are IEEE 754 floating-point values that JSON explicitly excludes.
If you run JSON.stringify({ value: undefined }) in JavaScript, the key is silently dropped from the output. If you try to stringify NaN, it becomes null. This silent conversion causes bugs that are genuinely hard to trace.
6. Hex numbers and leading zeros
{
"color": 0xFF0000,
"zipcode": 01234
}
JSON numbers must be decimal. No hex (0x), no octal (0o), no binary (0b). Leading zeros are also forbidden because some parsers interpret them as octal notation, which creates ambiguity. The zipcode above would need to be a string: "01234".
The number format JSON accepts is straightforward: an optional minus sign, digits (no leading zeros except for the number zero itself), an optional decimal point followed by digits, and an optional exponent. That's the complete grammar for JSON numbers.
7. Multiline strings
{
"description": "This is a
long description"
}
JSON strings cannot span multiple lines. If you need a newline in a string value, use the escape sequence \n. This catches people writing JSON by hand because it feels natural to break a long string across lines.
{
"description": "This is a\nlong description"
}
The Content-Type that matters
When serving JSON from an API, the correct Content-Type header is application/json. Not text/json (which is not a registered MIME type), not text/plain (which some browsers will refuse to parse as JSON for security reasons), and not application/javascript (which is for actual JavaScript code).
Debugging JSON from the command line
The jq command-line tool is the fastest way to validate and explore JSON. If you don't have it installed, you should.
Validate a file:
jq . data.json
If the JSON is invalid, jq prints an error pointing to the exact line and character position. If it's valid, it pretty-prints the output.
Extract a field:
cat response.json | jq '.data.users[0].name'
Filter an array:
cat data.json | jq '.items[] | select(.price > 100)'
Format inline JSON from a curl response:
curl -s https://api.example.com/data | jq .
For quick validation without installing anything, Python works too:
python3 -m json.tool < data.json
This reads from stdin, validates the JSON, and pretty-prints it. If the JSON is invalid, it prints the error with a line number.
JavaScript objects are not JSON
I keep coming back to this because it's the root cause of most of these mistakes. When you write const obj = { name: "Michael" } in JavaScript, that's an object literal. It follows JavaScript syntax rules. When you call JSON.stringify(obj), the output follows JSON syntax rules. They overlap but they aren't identical.
The test is simple: if you can't paste it into JSON.parse() and get back the same data, it's not valid JSON.
For quick formatting and validation without setting up a local environment, I keep a JSON formatter at zovo.one/free-tools/json-formatter that highlights syntax errors and shows you exactly where your JSON breaks.
Knowing the spec saves hours of debugging. The rules are small enough to memorize. Double-quoted keys. Double-quoted strings. No trailing commas. No comments. No undefined. No hex. No multiline strings. Seven rules. That's JSON.
I'm Michael Lip. I build free developer tools at zovo.one. 350+ tools, all private, all free.
Top comments (0)