ToolsAre.Us — Next-Gen Tools Hub ToolsAre.Us — Next-Gen Tools Hub

JSON: the rules, and the places it bites

A deliberately tiny format — and the handful of ways it quietly loses your data.

Last updated 2 September 2026 · ToolsAre.Us Guides

JSON's great virtue is that it is small enough to hold in your head. The entire grammar fits on a postcard, which is why it displaced heavier formats almost everywhere. But a few of its omissions and one inherited numeric limitation cause real, quiet data loss, and they are worth knowing before they cost you an afternoon.

The whole format

JSON has exactly six kinds of value:

That is the complete list. Everything below follows from what is not on it.

What is not allowed, and trips people constantly

The number problem, which is the serious one

This is the pitfall that silently corrupts data rather than raising an error.

The JSON specification describes number syntax but deliberately says nothing about precision or range. It is left to the implementation — and the overwhelmingly common implementation, JavaScript's, parses every number into an IEEE 754 double-precision float.

A double represents integers exactly only up to 253 − 1, which is 9,007,199,254,740,991. Beyond that, integers lose precision silently:

Sent:     { "id": 9007199254740993 }
Parsed:   9007199254740992        // off by one, no error raised

This is not hypothetical. Sixty-four-bit database identifiers, Twitter-style snowflake IDs, large financial figures in minor units, and high-resolution nanosecond timestamps all exceed the safe range routinely. The value round-trips through a system looking entirely plausible while pointing at the wrong record.

The fix is to transmit such values as strings. {"id": "9007199254740993"} survives intact through any parser. It is why so many APIs return an id and an id_str, which looks like redundancy until you know why it is there.

Floating point brings its own familiar consequence: 0.1 + 0.2 is not 0.3, because none of those values is exactly representable in binary. For money, store minor units as integers — pence, cents — or use a decimal string, and never a float.

Duplicate keys are undefined behaviour

{"a": 1, "a": 2} is, strictly speaking, valid JSON. The specification does not say what it means.

Most parsers keep the last occurrence. Some keep the first. Some raise an error. Some — in languages with multi-maps — keep both. Since two systems can therefore disagree about what the same document says, this is occasionally exploited deliberately: a document crafted so a validating service and a consuming service read different values.

Do not rely on any behaviour here. If you are writing JSON, do not emit duplicate keys; if you are consuming it from an untrusted source, consider rejecting documents that contain them.

There is no date type

JSON has no way to express a moment in time, so dates are conventionally strings — and the convention is ISO 8601: "2026-09-02T14:30:00Z".

Use it, and include the offset or a trailing Z for UTC. A string like "2026-09-02 14:30:00" with no zone information is ambiguous, and the receiving system will guess — usually as its own local time, usually wrongly. The time zones guide covers why that guess causes trouble and why future events need a zone identifier rather than a fixed instant.

Unix timestamps are the other common choice and are unambiguous, but they are unreadable to humans and hit the precision ceiling above if expressed in nanoseconds.

Strings, Unicode and the escaping edge case

JSON strings are Unicode. Characters may appear literally in UTF-8, or as \uXXXX escapes. Characters outside the Basic Multilingual Plane — emoji, many historic scripts — need a surrogate pair of two escapes when written in escaped form.

This creates a real edge case: it is possible to write a lone surrogate, half of a pair, which is valid JSON syntax but not valid Unicode. Parsers disagree on what to do, and such a string can fail when later written to a UTF-8 database. If you are generating JSON by string concatenation rather than with a proper serialiser, this is one of several reasons not to.

The mandatory escapes are the double quote, the backslash, and control characters below U+0020. A raw newline inside a string is invalid — it must be \n.

Key order

The specification defines an object as an unordered collection, so no meaning may be attached to key order. In practice most parsers preserve the order they encountered, and JavaScript objects preserve insertion order for string keys, so code that accidentally depends on order often works — until it meets a parser that sorts keys or a language whose maps do not preserve order.

If order matters, use an array. That is what arrays are for.

The variants, and when they are appropriate

FormatAddsUse for
JSONCCommentsConfiguration files read by a specific tool
JSON5Comments, trailing commas, unquoted keys, single quotes, hexHuman-edited configuration
NDJSON / JSON LinesOne JSON document per lineLogs and streams — each line parses independently, so a huge file needs no full parse and a truncated file is still mostly usable
JSON SchemaA validation vocabularyDescribing and enforcing document structure

None of these is JSON. Never send them to an interface expecting JSON, and be careful that a config file your editor accepts is not fed to a strict parser elsewhere.

Two practical notes

Never parse JSON by evaluating it as code. Passing a document to a JavaScript evaluator will appear to work, since JSON syntax is largely valid JavaScript, and it means any untrusted document can execute arbitrary code. Use the real parser. This has been a known and serious vulnerability class for a long time.

Pretty-print anything that will be diffed or reviewed. Minified JSON is one enormous line, so a version-control diff of a changed value shows the whole file as changed. Two spaces of indentation and one value per line makes changes legible, and the size difference is irrelevant for anything not being served at volume.

← All guides