Javascript is required
PitfallsPublished 2026-07-28Updated 2026-10-088 min read

Large JSON Integers: Precision Fixes

An order ID or database bigint can be correct in the raw HTTP response but change after JSON.parse. The JSON text is valid; the loss happens when the parsed value is stored as a JavaScript Number outside the safe-integer range. This article focuses only on integer precision, not large-file rendering or decimal-money arithmetic.

By: Tiny
JSONJavaScriptNumberBigInt精度丢失

1. Minimal reproduction: parsing can succeed with the wrong value

The following payload is valid JSON and JSON.parse does not throw, yet the parsed orderId no longer matches the source text. Silent rounding is more dangerous than a syntax error because it can survive until the ID is displayed, compared, or sent back to an API.

Inspect both the raw Network response and the in-memory value. Number.isSafeInteger provides a direct boundary check for integer Number values.

const raw = '{"orderId":9007199254740993,"count":42}';
const data = JSON.parse(raw);

console.log(data.orderId); // 9007199254740992
console.log(Number.isSafeInteger(data.orderId)); // false
console.log(Number.isSafeInteger(data.count)); // true

2. Root cause: JSON number syntax is not a JavaScript storage guarantee

JSON represents numbers as decimal literals, while each implementation chooses how to store them. JavaScript Number uses IEEE 754 binary64. Every integer from -(2^53 - 1) through 2^53 - 1 is exactly representable, exposed as Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER.

Beyond that range, adjacent integers can round to the same representable value. An identifier made only of digits is still an identifier; it should not become a Number merely because its wire representation looks numeric.

  • Number.MAX_SAFE_INTEGER is 9007199254740991.
  • Database bigint, Java long, and Go int64 ranges can exceed JavaScript safe integers.
  • Order IDs, account numbers, and trace IDs should normally be modeled as strings.

3. Preferred fix: define large integers as strings in the API contract

Serialize potentially large integers as JSON strings at the data producer and declare them as string in OpenAPI and TypeScript. Standard JSON.parse then preserves every decimal digit.

Convert to BigInt only when integer arithmetic is required. For routing, cache keys, and database identifiers, keeping the value as a string is simpler and avoids accidental Number/BigInt mixing.

type OrderResponse = {
  orderId: string;
  retryCount: number;
};

const raw = '{"orderId":"1819283748593829183","retryCount":2}';
const order = JSON.parse(raw) as OrderResponse;

console.log(order.orderId);
console.log(BigInt(order.orderId) + 1n);

4. A normal reviver cannot generally recover lost digits

A traditional JSON.parse reviver receives values after numeric conversion. Turning an already-rounded Number into BigInt only preserves the rounded value. When the server cannot change, obtain response.text() and use a parser designed to retain numeric source text or arbitrary-precision values.

Avoid regex-based rewrites that quote every long digit sequence. They can corrupt numbers inside strings, exponents, escaped content, or unrelated fields.

Some modern runtimes pass a third context argument for primitive reviver calls; context.source exposes the original JSON token. It can reconstruct a known field as BigInt, but compatibility must be verified and a string API contract remains the more portable design.

const raw = '{"id":9007199254740993}';
const wrong = JSON.parse(raw, (_key, value) =>
  typeof value === 'number' && !Number.isSafeInteger(value)
    ? BigInt(value)
    : value,
);

console.log(wrong.id); // 9007199254740992n

5. Serialize BigInt with a local replacer

JSON.stringify does not serialize BigInt by default. A local replacer is explicit and avoids changing global built-in behavior. The receiving API must also treat the resulting decimal text as a string.

function stringifyWithBigInt(value: unknown): string {
  return JSON.stringify(value, (_key, current) =>
    typeof current === 'bigint' ? current.toString() : current,
  );
}

console.log(stringifyWithBigInt({ id: 1819283748593829183n }));
// {"id":"1819283748593829183"}

6. Incorrect fixes and boundaries

Digit count is only a warning heuristic; Number.isSafeInteger checks the actual value. parseInt, Math.round, and toFixed cannot reconstruct digits that were already rounded.

BigInt is not a universal decimal-money solution because it stores integers only. Use minor-unit integers with explicit scale or a decimal type when fractional values and rounding rules matter.

  • Do not parse first and convert an unsafe Number to BigInt later.
  • Do not return the same field as number in one endpoint and string in another.
  • Do not mix Number and BigInt arithmetic without an explicit, safe conversion.
  • Keep ordinary counters and page numbers as Number when their range is well defined.

7. Verification checklist

Test values on both sides of the safe boundary, negative values, and representative production-shaped IDs. Assert both the runtime type and the exact decimal text.

const samples = [
  '9007199254740991',
  '9007199254740992',
  '9007199254740993',
  '-9007199254740993',
];

for (const id of samples) {
  const parsed = JSON.parse(`{"id":"${id}"}`) as { id: string };
  console.assert(parsed.id === id);
  console.assert(BigInt(parsed.id).toString() === id);
}

8. FAQ and conclusion

Can JSON itself carry integers larger than 53 bits? Yes, but interoperability depends on the receiver’s numeric implementation.

Can BigInt be placed directly in JSON? Not through default JSON.stringify; encode it as decimal text with an explicit policy.

Does a TypeScript string annotation fix a numeric payload? No. Runtime JSON must contain quotes around the value.

The durable fix is a string contract for identifiers, optional BigInt conversion for arithmetic, and explicit serialization when sending BigInt back. See /articles/json-unexpected-token for syntax failures and /articles/nested-json-escaping for embedded JSON strings.

Sources and further reading

Specifications and references for checking the article’s technical details.

Related articles

Keep exploring

Use the related browser tool to test the workflow with your own sample data.

Open related tool