JSON Integer Precision: When Two API Record IDs Become One
JSON numbers can lose identifier precision in JavaScript. See a local round-trip example and preserve large IDs before parsing changes their value.
By Yuki Yu
A support dashboard receives two records, North and South. Their identifiers end in 992 and 993. Both records arrive, the response parses successfully, and the dashboard displays only South. The disappearance happens in two steps. The parser turns the identifiers into equal JavaScript numbers. Then the second insert replaces the first value under the same map key.
JSON integer precision matters whenever an AI application passes identifiers between a model, a tool, a browser and a storage system. For an opaque identifier whose exact digits matter, carry it as a string across the boundary. If the field genuinely represents an integer for arithmetic, specify the permitted range and a compatible numeric representation in every consumer.
Read first. Establish the acceptance checks for the application that consumes a model response. A parser returning successfully is only one of those checks. Token360’s model evaluation framework.
Where does the last digit disappear?
It can disappear when a consumer converts a JSON number token to a numeric type with insufficient precision. The raw payload can contain the right digits. JavaScript’s ordinary JSON.parse produces a Number for a numeric literal. Around the positive safe-integer limit, that type cannot distinguish every adjacent integer. JavaScript’s maximum safe integer.
const north = JSON.parse('{"id":9007199254740992}').id;
const south = JSON.parse('{"id":9007199254740993}').id;
const records = new Map([[north, "North"], [south, "South"]]);
console.log(north === south, records.size, records.get(north));
// true, 1, "South"
A log of the map can make this look like an upstream duplicate. To locate the fault, compare the original number tokens, then the parsed values, then the map size. In this example the text tokens are distinct and the parsed values are equal. The map follows its normal replacement behavior. A different downstream consumer might reject the duplicate or show two rows that open the same detail page.
Which values survived our local round-trip?
We tested six decimal strings on November 3, 2026. For each, we built a JSON payload containing the digits as an unquoted integer, parsed it in JavaScript and compared the resulting integer with the exact input using BigInt. We also parsed a quoted-string version in JavaScript and the integer version using Python’s standard JSON decoder. No live model, customer identifier or paid API call was involved.
Input digits
JavaScript Number displayed as text
Exact integer retained?
Safe integer?
42
42
Yes
Yes
9007199254740991
9007199254740991
Yes
Yes
9007199254740992
9007199254740992
Yes
No
9007199254740993
9007199254740992
No
No
9007199254740994
9007199254740994
Yes
No
9223372036854775807
9223372036854776000
No
No
Four of the six integer cases retained the exact value in JavaScript. All six quoted strings preserved their digits there, and all six unquoted integers remained exact in the Python test. This is a deliberately chosen boundary set. The fraction four out of six is not an estimate of how often an application will corrupt IDs.
The last row also shows why the table labels the display column carefully. JavaScript’s default decimal formatting can print a short decimal representation of the stored floating-point value. For that row, converting the parsed number to BigInt yields 9223372036854775808. Neither that integer nor the displayed decimal recovers the input ending in 5807. Comparing using the exact source digits avoids using a formatted log line as ground truth.
Wire type
Distinct lookup keys
North lookup
South lookup
Number
1
South
South
String
2
North
South
Copy integer-precision-fixtures.json from the complete source below
“Exact for this value” and “distinguishable from its neighbors” answer different questions. The number 9007199254740992 equals two to the power of 53 and is representable. Its next neighbor rounds to that same value. If a team tests only the power of two, a successful round-trip can conceal a failure one record later.
RFC 8259 discusses interoperability across JSON implementations and identifies the range from −(253−1) through 253−1 as interoperable for integers among implementations using binary64 precision. The JSON grammar can still represent a longer integer token. A schema that validates it as an integer does not establish that a browser preserved the original identity after parsing. RFC 8259, section 6, Numbers.
How does this show up in an AI tool call?
Consider an assistant that looks up two invoices, offers a choice and passes the selected identifier to a details tool. The assistant could retrieve both documents correctly and quote the right amount, yet the UI could send the wrong identifier on the next click. A test that scores only the assistant’s prose would miss the failure.
For an opaque record ID, define the input and output field as a string and show a quoted example in the tool contract. Have the server return the string directly from an exact representation. Carry the same type through the client model, the cache key, the detail-link builder and the subsequent tool request. Quoting a value in the final request helps only if the digits survived all earlier stages.
A digit-only identifier can also carry formatting that arithmetic would discard. For example, a supplier may distinguish the identifier “00042” from another system’s “42”. Preserve the spelling specified by the owner. If two systems use different namespaces, retain the system identifier in the key as well. Precision handling fixes one kind of collision; it cannot distinguish two legitimate local IDs issued by different systems.
What if an existing provider already sends numeric IDs?
Inspect the earliest boundary you control. If the upstream bytes still contain the exact integer, a gateway or application parser that supports exact integers can read it there and emit an explicit string field for downstream use. If your application receives only an already-rounded Number, ask for an authoritative ID from the source. Converting that Number to a string or BigInt preserves the rounded value.
Treat a type change as an API migration. A client might sort a field numerically, validate it against an integer schema or use a numeric database column. Introduce a documented string field or a versioned contract, compare both fields where the old value is known to be safe, and update consumers before retiring the old field. Avoid silently accepting two wire types indefinitely without defining how each is interpreted.
For arithmetic quantities, make a separate decision. A bounded count may fit safely in a Number. A large exact integer may need explicit decimal-string transport and integer-aware arithmetic after validation. Specify whether negative values and leading zeros are allowed. An identifier policy should not change the behavior of unrelated numeric fields such as item counts.
What belongs in an integration test?
Assert end-to-end identity for at least two neighboring IDs, including one on each side of a precision boundary. Load both into the same collection, select each record separately and verify the exact identifier received by the final tool. Testing records one at a time can hide a collision that appears only when both share the same map.
Keep a safe control alongside the boundary cases. Check raw payload, parsed type, distinct-key count and final lookup. If the first three stages pass but the details view still selects the wrong row, inspect numeric casts in sorting code, URL construction and backend bindings. For a reproducible incident report, use synthetic identifiers with the same boundary behavior and record the consumer runtime. Token360’s API observability guide.
Frequently asked questions
Is a JSON number above the JavaScript safe range invalid JSON?
A large integer can be syntactically valid JSON. Its handling depends on the receiving implementation and the field contract.
Will BigInt repair a rounded Number?
It can express the integer represented by that Number. Recovering the original digits requires the exact original token or another authoritative source.
Should I convert all API numbers to strings?
Choose by field meaning. Opaque IDs need identity preservation. Quantities used in arithmetic need a documented range and numeric representation.
Why did the backend pass while the browser failed?
Different runtimes can parse the same token into different numeric types. In our test Python retained all six integer values and JavaScript Number retained four.
Does this imply that Token360 request IDs have this defect?
The fixtures are synthetic. The experiment tests a common integration pattern and makes no finding about the format or behavior of production Token360 request IDs.
Which regression check is easiest to keep?
Insert the two neighboring fixtures into the same map and assert that both labels remain independently retrievable. Then verify that the next tool receives the selected ID verbatim.