jsonloupe / guide open the app →

Why JSON.parse rounds long IDs, and how to fix it

A 64-bit ID goes into JSON.parse and a different number comes out. There is no error and no warning. This guide shows why it happens, which tools do it, and three ways to keep every digit.

The problem in two lines

JSON.parse('{"id":9007199254740993}').id
// 9007199254740992

The last digit changed from 3 to 2. A 19-digit ID loses more: 1234567890123456789 comes back as 1234567890123456800.

It gets worse when you write the data back, because the wrong number is now saved:

JSON.stringify(JSON.parse('{"id":9007199254740993}'))
// {"id":9007199254740992}

Nothing fails. The code keeps running with an ID that belongs to another record, or to no record at all.

Why it happens

JSON itself has no limit on the size of a number. The text 9007199254740993 is valid JSON. The limit comes from the program that reads it.

JSON.parse stores every number as a 64-bit float (IEEE 754 double). A float has 53 bits for the digits, so it can hold every whole number only up to 9,007,199,254,740,991. JavaScript calls that value Number.MAX_SAFE_INTEGER.

Past that point the float can hold only every second whole number, then every fourth, and so on. 9007199254740993 falls between two values a float can hold, so it moves to the nearest one.

The JSON standard warns about this. RFC 8259 says that whole numbers outside the range of ±(253 − 1) may not read back the same in other software.

Where you will meet it

Which tools round the number

ToolWhat happensHow to keep the digits
JavaScript JSON.parseRoundsUse a reviver or a lossless parser (below)
Go json.Unmarshal into anyRounds, because it uses float64Call Decoder.UseNumber(), or use an int64 field
Python json.loadsWhole numbers stay exact, decimals roundPass parse_float=Decimal
jq 1.7 and laterKeeps the digits until you do maths on themDo not calculate with the ID
ExcelKeeps 15 digits, the rest become zerosStore the ID as text

We ran the code rows on 2 November 2026 with Node 24, Go 1.27, Python 3.13 and jq 1.8.2.

Three ways to keep every digit in JavaScript

1. Send the ID as a string

If you own the API, this is the best fix. A string cannot be rounded, and every JSON reader handles it the same way.

{"id": "9007199254740993"}

An ID is a label. You never add two order numbers together, so it loses nothing by being a string.

2. Read the source text with a reviver

If you do not own the API, you can read the original text of each number. The reviver of JSON.parse receives it as context.source:

function keepLongIds(key, value, context) {
  if (typeof value !== 'number' || Number.isSafeInteger(value)) return value;
  return /^-?\d+$/.test(context.source) ? BigInt(context.source) : value;
}

const doc = JSON.parse(text, keepLongIds);
doc.id // 9007199254740993n

JSON.stringify refuses a BigInt, so use JSON.rawJSON to write the digits back:

JSON.stringify(doc, (key, value) =>
  typeof value === 'bigint' ? JSON.rawJSON(String(value)) : value);
// {"id":9007199254740993}

This works in Chrome 114, Firefox 135 and Safari 18.4 or later, and in Node 24.

3. Use a lossless parser

A lossless parser keeps each number as its original text until you ask for it. jsonloupe uses lossless-json for this:

import { parse, stringify } from 'lossless-json';

const doc = parse('{"id":9007199254740993}');
String(doc.id)  // '9007199254740993'
stringify(doc)  // '{"id":9007199254740993}'

How to check if your viewer rounds numbers

A JSON viewer built on JSON.parse shows you the rounded number. If you copy that ID into a query or a support ticket, you look up the wrong record. Paste this into your viewer or debug tool:

{"id": 9007199254740993, "price": 1.2345678901234567890}

If you see 9007199254740992, the tool changed the value before you read it.

Common questions

Is 9007199254740993 valid JSON?

Yes. The JSON grammar has no limit on the size of a number. Only the program that reads it has one.

Does TypeScript catch this?

No. Types are removed before the code runs. Writing id: bigint on an interface does not change what JSON.parse returns.

Does BigInt fix it?

Only with the steps above. BigInt holds whole numbers of any size, but JSON.parse never creates one, and JSON.stringify throws “Do not know how to serialize a BigInt”.

Check your own file. jsonloupe shows every number exactly as the file has it, and nothing is uploaded. Open jsonloupe →

More guides