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.
By sadiqbd Β· June 14, 2026
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 numbers β 9007199254740993 (a 16-digit integer) is a perfectly valid JSON number, per the specification β as 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 type β Number, 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 exactly β some consecutive integers become indistinguishable from each other, in this numeric representation.
9007199254740993 (2^53 + 1) cannot be represented exactly as a JavaScript Number β when 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 identifiers β database 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 9007199254740992 β a 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 intended β potentially 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 characters β no rounding, no precision loss β the 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 number β if 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 representation β which, 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 operations β which, for most "ID" use cases, is entirely appropriate β IDs 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) issue β some other languages' JSON parsers can represent large integers exactly, natively, without requiring the string-workaround β e.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 entirely β since 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
- For validating JSON containing large numbers: the formatter/validator operates on the JSON text itself β large 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)
- 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
- If consuming an API that returns large numeric IDs as JSON numbers (not strings) in a JavaScript context: be aware of the precision risk β if 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 standardJSON.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 insidious β 9007199254740993 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 relevant β but 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.