Your database stores one ID, but the browser sends back a neighboring number. This can happen before your application logic runs: a JSON parser converts an integer into a JavaScript Number that cannot represent it exactly. Formatting the response does not repair the original value.
Reproduce the change
Run this in a JavaScript console or Node.js. The unquoted ID is valid JSON, but the resulting Number loses precision:
const raw = '{"id":9007199254740993}';
const parsed = JSON.parse(raw);
console.log(parsed.id); // 9007199254740992
console.log(JSON.stringify(parsed)); // {"id":9007199254740992}
JavaScript's maximum safe integer is 9007199254740991, or 2 to the power 53 minus 1. Some larger integers are representable, but consecutive integers are no longer guaranteed to be distinguishable. For identifiers, that is enough to cause wrong record lookups and broken links.
Send opaque IDs as strings
If you control the API contract, serialize identifiers as strings at the producer. Quote the value before it crosses into a JavaScript Number:
const raw = '{"id":"9007199254740993"}';
const parsed = JSON.parse(raw);
console.log(parsed.id); // "9007199254740993"
console.log(typeof parsed.id); // "string"
An identifier is usually a label, not a measurement. Keep it as a string in URLs, form fields, application state and caches. Converting it temporarily with Number(), parseInt() or a numeric form input can reintroduce the same bug. A string contract also preserves meaningful leading zeros and nonnumeric IDs.
When changing an existing API, document the new type and test every consumer. A response that sometimes uses strings and sometimes uses numbers is harder to handle reliably than one consistent schema.
BigInt helps only if it receives the original digits
Use BigInt when you genuinely need integer arithmetic. Construct it from a decimal string, not from a Number that has already rounded:
const exact = BigInt("9007199254740993");
const alreadyRounded = BigInt(Number("9007199254740993"));
console.log(exact.toString()); // "9007199254740993"
console.log(alreadyRounded.toString()); // "9007199254740992"
Calling BigInt after a normal parse cannot reconstruct discarded digits. The rounded result does not tell you which original integer produced it.
For serialization, convert intentionally selected BigInt fields to strings:
const count = 9007199254740993n;
const body = JSON.stringify({ count: count.toString() });
console.log(body); // {"count":"9007199254740993"}
Ordinary JSON.stringify() does not serialize a BigInt directly. Avoid silently replacing every integer in a payload with a different type unless that transformation is part of your documented contract.
What if the API cannot change?
Keep the original response text until you have chosen a parser that preserves numeric tokens. A third-party parser may represent large values as strings, BigInts or decimal objects; check its behavior for nested arrays, negative values, decimal fractions and scientific notation.
Some newer JavaScript environments expose the original token through the JSON.parse reviver context. Availability varies, so check the runtime you actually deploy. A normal two-argument reviver receives an already-converted value and cannot recover lost digits on its own.
Do not repair JSON by using a regular expression to quote anything that looks numeric. Numbers can occur inside strings, escaped text, arrays and exponent notation. A textual substitution can change valid data or create invalid JSON.
Formatting is another parsing step
The QuickWebTools JSON Formatter uses native JSON parsing. Quote large IDs before using it, or use a precision-preserving parser for a payload that contains unquoted large integers. The formatter is useful for checking structure and syntax; it is not a guarantee that every numeric token keeps its original digits.
To investigate a change, compare the untouched response text with the reserialized output using the Diff Checker. Use synthetic or redacted examples so you can share the reproduction without exposing account data.
A small regression checklist
- Test IDs just below, at and above 9007199254740991.
- Verify that request and response schemas agree on the ID's type.
- Check URL builders and form controls for implicit Number conversion.
- Test nested records and arrays, not only a top-level field.
- Assert exact string equality after a full API round trip.
For ordinary counts, use Number.isSafeInteger() when your application requires exact integers. Decimal arithmetic has additional concerns: putting a decimal amount inside the safe integer range does not make every fraction exactly representable.