concept

JSON Number Precision Limit

also called Safe Integer Range, IEEE 754 Interoperability Limit

The point above which a JSON integer is silently rounded by any parser backed by a double - which corrupts 64-bit identifiers with no error at any layer.

jsonieee-754identifiersinteroperabilitysilent corruption

A marketplace exposes 64-bit listing identifiers over a JSON API. A partner's browser integration reports that roughly one listing in a thousand cannot be fetched: the identifier it sends back differs from the one it received in the last two or three digits, and the server logs a well-formed request for a listing that does not exist.

JSON has one number type and no precision rules of its own. RFC 8259 (2017) observes that implementations commonly use IEEE 754 double-precision floating point, and that good interoperability is achieved when numbers stay within the range a double represents exactly: ±(2^53 − 1), or 9,007,199,254,740,991.

A 64-bit identifier can exceed that by three orders of magnitude. A JavaScript client parsing 9007199254740993 receives 9007199254740992. The parse succeeds, the value is wrong, no error is raised anywhere, and the resulting identifier is well-formed enough to pass schema validation.

Why it matters

It is one of a small number of defects that are undetectable in band. There is no layer at which the loss can be observed: the server serialised a valid integer, the transport delivered it intact, and the client's parser did exactly what the floating-point standard requires. The information is gone before any validation sees it.

It also hides through the entire test cycle, because test fixtures use small values. The failure appears only once the identifier space crosses 2^53, which can be years after launch and long after the API's contract has become hard to change. The cost is therefore paid at the worst moment, by partners, on data that looks valid.

Implementation patterns

  • Serialise identifiers as strings. An identifier is an opaque token, not a quantity - nothing is lost by quoting it, and no client can round it.
  • On an existing API, add the string field beside the numeric one, document the precision risk explicitly rather than assuming partners know, and deprecate the numeric field on a published timetable. Twitter's API is the documented precedent: it added id_str next to the numeric id because browser clients were truncating its identifiers.
  • Keep genuine numbers in the safe range, or send them as strings with an explicit scale. Monetary amounts as floating-point numbers have the same class of problem for a different reason, and the same answer: a string with a stated scale, or an integer of minor units.
  • Put a value above 2^53 in the contract-test fixtures. One fixture closes this permanently and almost no API has it.
  • Check the other boundaries, because the API is where it is noticed and rarely where it happens: spreadsheet imports, BI tools that infer column types from a sample, log pipelines that parse JSON into typed fields, and stores whose default numeric type is a double.

Industry example

Twitter's move to snowflake identifiers in 2010 pushed its ids past the safe range, and the documented response was the id_str field - a string copy of every identifier - precisely because JavaScript clients were silently rounding the numeric form. The pattern was widely copied and is now the default advice for any public API with large identifiers.

The complementary data point is from the schema side: Protocol Buffers' JSON mapping specifies that 64-bit integer types are represented as strings in JSON, for exactly this reason. When two independent ecosystems converge on quoting integers, that is a strong signal about where the default should be - and a reason not to treat it as a special case to be argued each time.

Failure scenarios

  • Identifier truncation, as above: a fraction of requests referencing records that do not exist, with the failure rate growing as the identifier space grows.
  • Silent amount corruption where a high-precision decimal is sent as a number and comes back rounded, which in a payments context is a reconciliation break rather than a 404.
  • Idempotency keys derived from a rounded identifier, so two distinct operations collide on one key - a correctness failure far worse than the original rounding.
  • BI and spreadsheet imports typing a 19-digit identifier as a number and destroying it, with the analysis built on top of it unaware.
  • Round-trip corruption through a middleware component that parses and re-serialises JSON - an API gateway, a logging proxy, a transformation step - even when both endpoints handle 64-bit integers correctly.

Trade-offs

Choose Gains Pays
Identifiers as strings immune to rounding - opaque so the format can change slightly larger payloads - clients that expect a number must change
Numeric identifiers in the safe range nothing to explain - natural ordering a ceiling at about 9 × 10^15 that must be designed for
Both fields published compatible with existing clients - a migration path two representations to keep consistent - the numeric one stays wrong
Numeric with documentation only no code change relies on every client author reading the note - which is how this happens

When not to use it

Where a value genuinely is a number used in arithmetic - a count, a measurement, a page size - keep it a number and keep it in the safe range. Quoting everything numeric makes an API tedious to use and pushes parsing work onto every client, so the rule is specifically about identifiers and high-precision decimals.

And in a closed estate where every client is a language with native 64-bit integers, the risk is theoretical. Be aware that "closed" rarely survives contact with a browser, a spreadsheet, a partner or a new BI tool - which is why public APIs should quote from the start, even when the first clients would be fine.

Interview question

Q: A partner reports that about one in a thousand of your records cannot be fetched by the identifier your API returned. Server logs show a valid request for a missing record. Diagnose it, and tell me how you would fix it for an API that already has 400 integrations.

What a strong answer covers: JSON's single number type and IEEE 754 double precision, with the ±(2^53 − 1) boundary; why the error is always in the least significant digits and only for large values; why nothing can detect it in band; the fix of adding a string field beside the numeric one with a deprecation timetable rather than a breaking change; the contract-test fixture that prevents regression; and the adjacent boundaries - gateways, logging, BI tools - where the same rounding happens outside the API.

Quick check

Quiz: What is the largest integer a JSON consumer can be relied on to parse exactly, and why that number? 9,007,199,254,740,991, which is 2^53 − 1 - the limit of exact integer representation in an IEEE 754 double, the type most JSON parsers use for numbers.

Flashcard: Why is a 19-digit identifier in JSON corrupted with no error anywhere? Because the parser represents it as a double, rounds the low bits, and returns a well-formed number - so the parse succeeds, schema validation passes and the value is simply wrong.