跪拜 Guibai
← All articles
Frontend

JSON.parse Silently Corrupts Large Integers—and the Reviver Can't Save You

By 前端阿凡 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A single silently-corrupted ID can route users to wrong records, break audit trails, or cause data writes against the wrong entity—all without a stack trace. Most teams won't catch this in development because small test IDs parse fine; the bug only surfaces in production with real large identifiers.

Summary

A production bug where user detail pages kept loading the wrong person traced back to a 19-digit user ID. The backend stored and transmitted the correct number, but JSON.parse silently rounded the last digits, producing a different ID. No error was thrown, no warning logged—just silently corrupted data.

The root cause is JavaScript's IEEE 754 double-precision limit: integers larger than 9,007,199,254,740,991 lose precision. A common frontend fix using the reviver parameter fails because the number is already truncated before the reviver runs. The only reliable client-side path is a library like json-bigint that converts large numbers to BigInt during parsing.

The simplest fix is on the backend: serialize any integer over 16 digits as a string. For teams on gRPC or protobuf, proto3's int64 already defaults to string in JavaScript, sidestepping the issue entirely.

Takeaways
JavaScript's safe integer ceiling is ±9,007,199,254,740,991 (2⁵³-1); anything larger gets rounded.
JSON.parse returns the corrupted number with no error, exception, or console warning.
The reviver callback in JSON.parse receives the already-truncated value, so wrapping it with String() still produces the wrong number.
json-bigint is the only reliable client-side fix because it converts large numbers to BigInt during parsing, before truncation occurs.
Backend serialization to string is the simplest universal fix: Jackson's ToStringSerializer for Java, json:"userId,string" tag for Go, lossless-json for Node.
proto3's int64 defaults to string in JavaScript environments, so gRPC/protobuf projects are already protected.
Scanning API responses with Number.isSafeInteger() on every numeric field catches the risk before it reaches production.
Conclusions

The reviver-based fix is widely shared online as a solution but is fundamentally broken—it operates on already-corrupted data. This makes it a dangerous piece of cargo-cult advice.

The bug's non-deterministic nature (only large IDs trigger it) means it survives QA and staging, making it a classic 'works on my machine' production time bomb.

The JSON spec itself has no integer size limit, but JavaScript's number type does—this is a language-level impedance mismatch, not a bug in either system, which is why blame ping-pongs between frontend and backend teams.

Concepts & terms
IEEE 754 double-precision floating-point
The 64-bit binary format JavaScript uses for all numbers. It can exactly represent integers only within ±2⁵³-1; beyond that, the gap between representable numbers exceeds 1, causing rounding.
Number.isSafeInteger()
A built-in JavaScript method that returns true if a value is an integer within the safe range where it can be exactly represented and compared without rounding errors.
JSON.parse reviver
A callback function passed as the second argument to JSON.parse that transforms each parsed value. It runs after the parser has already converted the JSON text into JavaScript values, so large numbers are already truncated before the reviver sees them.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗