CurlyJSON
Tools
← Blog

Your JSON tool just changed this ID

JavaScript can silently change large integers when it parses perfectly valid JSON. That matters for database IDs, API responses, and the tools you use to look at them.

Paste this into a JSON formatter that uses JavaScript’s built-in parser and press Format:

{ "orderId": 9007199254740993, "amount": 1.50 }

You get this back:

{ "orderId": 9007199254740992, "amount": 1.5 }

The ID changed by one, and no error appeared. If you copy that output into a ticket, a test fixture or a support reply, you’re now pointing at a different order.

Why it happens

JSON.parse turns every number into a JavaScript Number, a 64-bit floating-point value. Every integer up to Number.MAX_SAFE_INTEGER can be stored exactly:

Number.MAX_SAFE_INTEGER // 9007199254740991  (2^53 − 1)

Above that, only some integers can be stored. Between 2^53 and 2^54 only the even ones fit, and the gaps get wider from there. An integer that doesn’t fit is rounded to the nearest one that does:

JSON.parse('{"id": 9007199254740993}').id        // 9007199254740992
JSON.parse('{"big": 12345678901234567890}').big  // 12345678901234567000
JSON.parse('{"amount": 1.50}').amount            // 1.5

You can reproduce this in any browser console or with node -e. Not every large integer changes: 9007199254740992 is stored exactly. The problem is that you can’t tell which ones did from the parsed value alone.

The last line is a different kind of change. 1.50 and 1.5 are the same number, so nothing is lost numerically, but the original spelling is gone: a tool that writes the result back out with JSON.stringify can’t give you what you wrote.

JSON input After JSON.parse + JSON.stringify What changed
9007199254740993 9007199254740992 Integer rounded
12345678901234567890 12345678901234567000 Integer rounded
1.50 1.5 Trailing zero dropped, value unchanged
1e400 null Too large: parsed as Infinity, which JSON can’t represent
"9007199254740993" "9007199254740993" Nothing: strings are kept as written

Does JSON itself limit the size of numbers?

No. JSON’s grammar allows any number of digits. The JSON specification, RFC 8259, lets implementations set their own limits, and notes that integers between −(2^53 − 1) and 2^53 − 1 are the ones every implementation using double-precision numbers will agree on. The rounding comes from JavaScript’s Number, not from JSON.

Where it bites in practice

  • 64-bit database IDs (BIGINT) once their values pass 2^53, for example IDs generated from timestamps or random bits rather than a small counter.
  • Snowflake-style IDs, used by X (Twitter), Discord and many internal systems, which are far above 2^53.
  • Money and quantities sent as numbers, where the exact digits and trailing zeros matter to the reader.
  • Debugging: you compare a log line with what the API returned, and the formatter quietly shows a different number from the one on the wire.

The dangerous part is that nothing fails. The JSON is valid before and after.

The same rounding can make two different IDs look equal when you diff two responses. Comparing JSON responses safely covers the other traps in diffing two responses.

Can a JSON formatter change large numbers?

Yes, if it’s built on JSON.parse and JSON.stringify. Formatting looks like a purely cosmetic change, adding indentation, but a formatter that parses the document into JavaScript values and serializes it again passes every number through Number on the way. The output is valid JSON with different numbers in it.

A tool that formats the text without converting numbers keeps them as written. To find out which kind you’re using, paste this and check the output:

{ "a": 9007199254740993, "b": 12345678901234567890, "c": 1.50, "d": 1e400 }

A tool built on JSON.parse and JSON.stringify shows 9007199254740992, 12345678901234567000 and 1.5, and turns 1e400 into null. If every value comes back unchanged, the tool kept the numbers for this test. That’s a good sign, not a guarantee for every operation, so also test the transformations you actually use.

The CurlyJSON formatter uses its own parser instead of JSON.parse and keeps every number exactly as written: 9007199254740993 stays 9007199254740993, 1.50 stays 1.50, and 1e400 stays 1e400. It runs in your browser, so the response you paste isn’t uploaded. JSON Diff and the JSONPath playground use the same parser, so big numbers stay exact there too.

How to keep large integers exact in your code

The usual fix on the API side is to send large IDs as strings: "orderId": "9007199254740993". Discord’s API returns its snowflake IDs as strings for this reason, and Twitter’s API returned every ID twice: as the number id and as the string id_str. If you control the API, that’s the most robust option.

In JavaScript code that must read large integers, use a parser with BigInt support, or the reviver’s third argument from JSON.parse source text access (a Stage 4 TC39 proposal), where your runtime supports it. context.source gives you the number exactly as written, so you can convert it with BigInt(context.source).

To find numbers that may have been rounded, check parsed integers with Number.isSafeInteger. A false result means the value is outside the safe range and may have changed; it doesn’t prove that it did.

Free, private, and runs in your browser — nothing you paste is uploaded.

Open the JSON Formatter →