JSON Formatting and Validation Guide
JSON (JavaScript Object Notation) is the default format for APIs and configuration files. Its grammar is small, which is also why parsers are strict: one stray character makes the whole document invalid. This guide covers what the specification requires, the errors that cause most failures, and the formatting choices that are left to you.
What the standard requires
JSON is defined by RFC 8259 (and the equivalent ECMA-404). The rules that matter in practice:
- A document is a single value: an object, array, string, number,
true,falseornull. - Object keys are strings, and strings use double quotes only.
- Items are separated by commas, with no comma after the last item.
- Numbers are decimal, with no leading zeros, no leading
+, no hex, and noNaNorInfinity. - There are no comments.
- Text must be Unicode, and RFC 8259 requires UTF-8 when exchanged between systems. Control characters inside strings must be escaped, for example
\nfor a newline.
The most common syntax errors
Trailing commas
// Invalid
{ "name": "Ada", "tags": ["a", "b",], }
// Valid
{ "name": "Ada", "tags": ["a", "b"] }
Single quotes
// Invalid
{ 'name': 'Ada' }
// Valid
{ "name": "Ada" }
Unquoted keys
// Invalid (valid JavaScript, but not JSON)
{ name: "Ada" }
// Valid
{ "name": "Ada" }
Comments
// Invalid
{
"port": 8080 // dev port
}
// Valid: remove the comment, or store it as data
{ "port": 8080, "_comment": "dev port" }
NaN, Infinity and undefined
// Invalid
{ "ratio": NaN, "max": Infinity, "extra": undefined }
// Valid: use null, or omit the key
{ "ratio": null, "max": null }
Note that JavaScript's JSON.stringify turns NaN and Infinity into null and drops keys whose value is undefined, so the output is always valid but can silently lose information.
Other frequent causes: an unescaped double quote or raw line break inside a string, mismatched brackets, and a byte-order mark or stray text before the first brace. Parser error messages usually give a line and column or a character position. The real mistake is often on the line before it, such as a missing comma.
Pretty-print vs minify
Both forms are the same data. Only whitespace differs, and whitespace between tokens is ignored.
- Pretty-print when humans read it: config files in version control (clean line-by-line diffs), API responses you are debugging, documentation and test fixtures.
- Minify when bytes matter: payloads sent over the network, values stored in a database column or a cookie, and data embedded in HTML.
Minifying typically saves 10–30% of size on nested data, though HTTP compression such as gzip narrows the gap considerably. Use the JSON formatter to validate, pretty-print or minify in one step. It runs in your browser, so the data is not uploaded.
Indentation conventions
The specification does not set an indent. Common practice is 2 spaces in the JavaScript ecosystem (package.json is written this way by npm) and 4 spaces in much Python and Java tooling. Tabs are less common but valid. In JavaScript, JSON.stringify(value, null, 2) produces 2-space output. What matters is consistency inside a project, ideally enforced by a formatter so diffs stay small.
JSON vs JSON5 and JSONC
Some tools accept supersets of JSON that relax the rules above:
| Format | Allows | Typical use |
|---|---|---|
| JSON | Strict RFC 8259 only | APIs, data exchange |
| JSONC | JSON plus comments (and often trailing commas) | Editor settings such as VS Code settings.json, tsconfig.json |
| JSON5 | Comments, unquoted keys, single quotes, trailing commas, hex numbers, NaN and Infinity | Hand-edited config files |
A standard JSON parser rejects both JSONC and JSON5 files, so use a dedicated parser for them and never send them to an API that expects JSON.
Large numbers lose precision in JavaScript
The JSON grammar puts no limit on the size of a number, but JavaScript parses every number as a 64-bit float. Integers are exact only up to 2^53 − 1, which is 9,007,199,254,740,991. Beyond that, values are silently rounded:
JSON.parse("9007199254740993") // 9007199254740992
JSON.parse("12345678901234567890") // 12345678901234567000
This commonly breaks 64-bit database IDs and snowflake-style identifiers. The safe fix is to have the API send such values as strings ("id": "9007199254740993"). If you control the parser, revive them as BigInt, or use a parser that preserves big integers. Other languages such as Python keep arbitrary-precision integers, so the problem often only shows up on the JavaScript side.