You paste {"id":9007199254740993} into a tool, copy the result, and the last digit changed. Nobody edited the payload. The parser did - quietly - because the receiving language’s number type cannot represent every integer exactly.
This article explains JSON’s numeric text versus JavaScript’s safe integer range, shows a verified before/after, and clarifies when an API should send identifiers as strings.
JSON numbers are decimal text
In the JSON grammar, a number is a sequence of digits (with an optional sign, fraction, and exponent). The file or response does not say “IEEE-754 double” or “64-bit int.” The parser decides what machine value that digit string becomes.
Many languages map JSON numbers to floating-point doubles. Doubles have a 53-bit significand, so integers only stay exact through Number.MAX_SAFE_INTEGER in JavaScript: 9007199254740991 (2^53 − 1). Beyond that, adjacent integers share the same double representation. The last digit of a large ID can round away the moment JSON.parse runs.
That is why “the formatter changed my ID” often means “the host language rounded my ID.”
The reproducible example
Input text:
{"id":9007199254740993}
Native JavaScript (Node 22 / browser JSON.parse):
JSON.parse('{"id":9007199254740993}')
// → { id: 9007199254740992 }
JSON.stringify({ id: 9007199254740993 })
// → '{"id":9007199254740992}'
The literal 9007199254740993 is not a safe integer. Parsing and re-serializing with the default number type yields 9007199254740992. That last digit mattered - and it is gone.
Pretty Payload (same input, verified against the current repair/viewer stack):
- Displays
idas 9007199254740993 - Copy JSON / Copy Minified emit
9007199254740993again
The viewer keeps supported large integer literals via an exact digit carrier (BigNum in the parser), then writes those digits back out. This is not arbitrary decimal precision for every float, scientific notation, or downstream app. It preserves the integer text you pasted for inspection and copy inside Pretty Payload.
Try the sample in the free web demo and compare what you see with what JSON.parse does in DevTools.
What this does - and does not - claim
| Claim | Accurate? |
|---|---|
| Supported large integer IDs keep their digits in Pretty Payload’s tree and copied output | Yes, for the integer-literal case shown above |
| Every tool, SDK, or database will preserve those digits | No - check each consumer |
| Arbitrary high-precision decimals / floats are guaranteed | No - do not treat this as a general big-decimal library |
| You should change an API’s types without checking its contract | No - only when identifiers were never meant to be math |
If another system must round-trip the ID unchanged, the durable fix is often to represent identifiers as JSON strings ("id":"9007199254740993"), assuming the API contract allows it. Many platforms (including some CRM and ERP APIs) already do that for that reason.
Practical debugging tips
- Compare the raw response bytes to what your client library exposes. If the wire has
...993and your object has...992, the loss happened at parse time. - Don’t “fix” the ID by hand after a float round-trip - you may invent a different wrong value.
- Inspect locally with a viewer that preserves the digits for the session, then copy a smaller example for the ticket (free online viewer guide).
- Search nested payloads when the ID sits under several layers (nested JSON viewer).
That last digit is the whole plot of many ID bugs. JSON wrote the digits faithfully; the number type edited the ending without asking.
Try it
Paste this into prettypayload.com/try/:
{"id":9007199254740993,"label":"demo_1042"}
Confirm the tree shows 9007199254740993, then use Copy JSON. Optionally paste the same text into a DevTools console with JSON.parse to see the native rounding side by side.
For clipboard-heavy work, add Pretty Payload to Chrome.