Try the JSON Formatter

Why 9007199254740993 Becomes 9007199254740992: JSON Numbers and JavaScript's Precision Limit

A JSON number like 9007199254740993 can become 9007199254740992 just by being parsed in JavaScript — not a bug in the parser, but a mismatch between JSON's unlimited-precision number specification and JavaScript's double-precision floating point, which can't represent integers above 2^53 exactly. Here's why this specifically affects large database IDs, the common "represent as string" workaround, and why this creates cross-language inconsistencies when backends and JavaScript frontends disagree about what "the same number" means.

June 14, 2026 6 min read
Share: Facebook WhatsApp LinkedIn Email
Why 9007199254740993 Becomes 9007199254740992: JSON Numbers and JavaScript's Precision Limit

A JSON number like 9007199254740993 can be silently changed to 9007199254740992 just by being parsed in JavaScript — not because of a bug, but because JSON's number format and JavaScript's number type have different precision limits, and large integers fall into the gap between them

The previous articles on this site covered JSON vs other formats, schema validation, and structured logging. This article addresses a specific, genuinely surprising issue: large integers in JSON can be silently corrupted during parsing — not due to malformed JSON, but due to a fundamental mismatch between JSON's number specification and JavaScript's (and some other languages') numeric type.


JSON numbers: unlimited precision, in the specification

The JSON specification itself places no limit on the size/precision of numbers9007199254740993 (a 16-digit integer) is a perfectly valid JSON number, per the specificationas valid as 1 or 3.14.

However: the JSON specification also doesn't mandate how a parser must represent numbers internally — it specifies the textual format numbers must follow in JSON documents, but leaves the internal, parsed representation up to each implementation/language.


JavaScript's number type: IEEE 754 double-precision floating point

JavaScript has (traditionally) had a single numeric typeNumber, implemented as IEEE 754 double-precision floating point — the same format used for "double"/"float64" in many languages.

Double-precision floating point can represent integers exactly up to 2^53 (= 9,007,199,254,740,992)beyond this value, not all integers can be represented exactlysome consecutive integers become indistinguishable from each other, in this numeric representation.

9007199254740993 (2^53 + 1) cannot be represented exactly as a JavaScript Numberwhen JSON.parse encounters this value in a JSON document, it gets rounded to the nearest representable double — which, for 9007199254740993, is 9007199254740992 (2^53) — the parsed JavaScript value silently differs from the original JSON text, by 1.


Why this is dangerous, specifically for IDs

This precision issue is largely irrelevant for "normal" numbers (prices, quantities, most everyday numeric values — which are almost always far below 2^53) — but becomes directly relevant for large, numeric identifiersdatabase IDs, especially "snowflake"-style or similar large auto-generated integer IDs (some systems generate IDs as large, 64-bit integers, which, for sufficiently active/long-running systems, can exceed 2^53).

The failure mode: a system sends JSON containing {"id": 9007199254740993, "name": "..."} — a JavaScript client JSON.parses this — the parsed id becomes 9007199254740992a different number (off by 1) — if the client then uses this id (e.g., in a subsequent API request, "fetch details for ID 9007199254740992") — it's requesting a different resource than intendedpotentially silently returning wrong data (if that other ID also exists and corresponds to something else), or an error (if that ID doesn't exist) — either way, a subtle, hard-to-diagnose bug, triggered only for IDs above 2^53, which, for systems with many IDs generated over time, might initially be rare (early IDs are small) but become increasingly common as ID values grow.


The common workaround: represent large IDs as strings in JSON

A widely-adopted solution: for numeric values that might exceed 2^53 (large IDs, primarily) — represent them as JSON strings, not JSON numbers:

{"id": "9007199254740993", "name": "..."}

JSON strings have no numeric-precision limitation"9007199254740993" is parsed, by any JSON parser in any language, as a string containing exactly those 16 charactersno rounding, no precision lossthe value is preserved exactly, because it's never interpreted as a number at all during parsing.

The trade-off: the client now receives a string, not a numberif the client needs to perform numeric operations on this value (arithmetic, comparisons as numbers) — it would need to convert the string to some numeric-like representationwhich, for values exceeding 2^53, JavaScript's standard Number type still cannot represent exactly — requiring either BigInt (a more recently-added JavaScript type, supporting arbitrary-precision integers — though JSON.parse itself doesn't automatically produce BigInt values; converting a string to BigInt is a separate, explicit step) or treating the value as an opaque identifier (never needing numeric operationswhich, for most "ID" use cases, is entirely appropriateIDs are typically used for equality/lookup, not arithmetic).


Other languages: some have arbitrary-precision integers natively

This issue is primarily a "JavaScript" (and similar, double-based-number languages) issuesome other languages' JSON parsers can represent large integers exactly, natively, without requiring the string-workarounde.g., some languages' default JSON-parsing behavior might parse large integers into an arbitrary-precision integer type (rather than a fixed-precision float) — for such languages, 9007199254740993, as a JSON number, would be parsed exactly, with no precision loss.

This creates a cross-language inconsistency: if a backend (in a language with exact large-integer parsing) sends {"id": 9007199254740993} (as a JSON number) — and a JavaScript frontend consumes this — the backend's representation was exact; the frontend's parsed value is not* — the "bug" exists specifically at the JavaScript-consuming end, even though the JSON itself was, per the specification, entirely valid and the backend's handling was entirely correct.

The string-for-large-IDs workaround avoids this cross-language inconsistency entirelysince strings are parsed identically (and exactly), by every language's JSON parser, regardless of that language's native numeric-precision characteristics.


How to use the JSON Formatter on sadiqbd.com

  1. For validating JSON containing large numbers: the formatter/validator operates on the JSON text itselflarge integers, as text, remain exactly as written (the precision issue arises during parsing into a specific language's numeric type*, not during text-level validation/formatting)
  2. If you control an API's output: for any numeric field that might, over time, exceed 2^53 (database IDs being the primary case) — consider representing these as JSON strings, proactively, rather than waiting for the precision-loss bug to manifest as IDs grow past the threshold
  3. If consuming an API that returns large numeric IDs as JSON numbers (not strings) in a JavaScript context: be aware of the precision riskif the API can't be changed (third-party API), consider whether BigInt-based parsing (custom JSON parsing that preserves large-integer precision, rather than relying on standard JSON.parse) is necessary for correctness

Frequently Asked Questions

How would I even notice this bug is happening, if the numbers "look the same" in most debugging output? This is part of what makes this bug particularly insidious9007199254740993 and 9007199254740992 look almost identical* when displayed — a casual glance at logged/displayed values might not reveal the off-by-one difference. Symptoms that might indicate this issue: API requests for "ID X" intermittently returning data for "ID X-1" instead (or errors, if X-1 doesn't exist) — particularly if this correlates with IDs above a specific, large threshold (around 2^53 ≈ 9 quadrillion) — *if your system's IDs are nowhere near this magnitude (most systems' IDs, for a long time, won't be) — this specific issue isn't yet relevantbut for systems with very large, long-running ID sequences (or certain ID-generation schemes that produce large values from the start), it's worth proactively checking.

Is the JSON Formatter free? Yes — completely free, no sign-up required.

Try the JSON Formatter free at sadiqbd.com — validate, format, and inspect JSON, including checking for numbers that may exceed safe integer precision.

Share: Facebook WhatsApp LinkedIn Email

JSON Formatter

Free, instant results — no sign-up required.

Open JSON Formatter →
Similar Tools
URL Encoder/Decoder Hash Generator Timestamp Converter JSON Unescape & Cleaner Color Converter Password Generator Random String Generator Number Base Converter
JSON Formatter — Pretty Print, Minify & Validate JSON Instantly
Developer
JSON Formatter — Pretty Print, Minify & Validate JSON Instantly
JSON vs YAML vs MessagePack vs Protocol Buffers: Which Format for Which Use Case
Developer
JSON vs YAML vs MessagePack vs Protocol Buffers: Which Format for Which Use Case
JSON Schema Validation and API Contracts: OpenAPI, Contract Testing, and Validation Libraries
Developer
JSON Schema Validation and API Contracts: OpenAPI, Contract Testing, and Validation Libraries
Structured JSON Logging: How to Debug Production API Errors and Search Logs at Scale
Developer
Structured JSON Logging: How to Debug Production API Errors and Search Logs at Scale
Why JSON Doesn't Allow Comments — and the JSON5/JSONC Variants That Do (and Why They Break Standard Parsers)
Developer
Why JSON Doesn't Allow Comments — and the JSON5/JSONC Variants That Do (and Why They Break Standard Parsers)
Why JSON Parses Differently in JavaScript, Python, and Go — Precision, Duplicates, and Parser Security
Developer
Why JSON Parses Differently in JavaScript, Python, and Go — Precision, Duplicates, and Parser Security