JSON Toolkit

Por qué los enteros grandes pierden precisión

En resumen: Los enteros por encima de 2^53-1 pierden precisión en JSON.parse. Los IDs snowflake, los números de pedido y los bigint de base de datos se topan todos con esto, así que consérvalos como cadenas de extremo a extremo.

Intermedio~4 min de lecturaPublicado el 2026-10-30Actualizado el 2026-11-04
Por qué los enteros grandes pierden precisión en JSON y cómo conservarlos

En resumen: trata los IDs snowflake, los números de pedido y las claves primarias bigint como cadenas durante la serialización, el transporte y el análisis; conserva el tipo numérico solo cuando esté demostrado que el campo se mantiene dentro de 2^53-1, y entonces impónlo con una restricción maximum en el Schema.

Porque el daño ocurre durante el análisis, en silencio.

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

La cifra que falta se ha ido; no hay nada que recuperar.

De dónde viene 2^53

JavaScript tiene un solo tipo numérico: doble precisión IEEE 754, con 53 bits de significando (uno implícito, 52 explícitos). Por tanto los enteros exactos se detienen en ±(2^53-1):

-9007199254740991 .. 9007199254740991

Por encima de esa línea, los valores representables consecutivos están a 2, 4, 8 de distancia. 9007199254740993 es impar, y a esa magnitud todo valor representable es par, así que se redondea a 9007199254740992.

Campos afectados

Campo Dígitos Riesgo
IDs snowflake 18–19 Alto, las últimas cifras cambian
Números de pedido / transacción 16–20 Alto
Claves primarias bigint de base de datos 19 Alto
Dinero en unidades menores — Bajo
Marcas de tiempo en milisegundos 13 Seguro

El hilo común: son identificadores, no cantidades. Un identificador se transporta, nunca se suma ni se multiplica, así que la cadena es el tipo correcto.

Cuatro correcciones

Serializar como cadena. El backend convierte los campos bigint antes de escribir la respuesta. Simple y duradero; documenta que estos campos son cadenas.

Deja que el serializador lo gestione. El ToStringSerializer de Jackson para Long, la etiqueta json:",string" de Go. La misma idea: mantener el valor fuera del camino numérico.

Analizar con soporte de BigInt. json-bigint y bibliotecas similares. Ten en cuenta que BigInt no puede mezclarse con aritmética de Number, y que JSON.stringify lo rechaza.

Restringir el rango. Si el campo realmente no puede superar 2^53, añade maximum en el Schema para que los datos fuera de rango fallen la validación en lugar de fallar silenciosamente en el navegador.

Cómo saber si tienes este bug

Toma un ID conocido y hazle un round-trip: serializa, transmite, analiza y compara carácter por carácter. O inspecciona el cuerpo de la respuesta en crudo: un número de 19 dígitos sin comillas que llega con las últimas cifras equivocadas es casi con seguridad una pérdida en tiempo de análisis.

FAQ

¿Por qué JSON.parse cambia las últimas cifras sin dar error?

JavaScript tiene un único tipo numérico, la doble precisión IEEE 754, así que los enteros por encima de 2^53-1 solo pueden caer en el valor representable más cercano. JSON.parse lo trata como una conversión numérica normal: no lanza ni avisa.

¿Cómo puede el frontend conservar intactos los enteros grandes?

Usa una biblioteca como json-bigint, porque JSON.parse no puede hacerlo. El coste es que BigInt no puede mezclarse con aritmética de Number y que JSON.stringify lo rechaza, así que el código que lo rodea también tiene que cambiar.

¿Debería la documentación de la API indicar que estos campos son cadenas?

Sí. Serializar como cadenas es la corrección más simple y duradera, pero solo funciona si los consumidores tratan el valor como texto, así que el tipo del campo tiene que estar escrito en el contrato de la API.

Lectura adicional