Чому великі цілі числа втрачають точність
Головне: Цілі числа понад 2^53-1 втрачають точність у JSON.parse. ID-сніжинки, номери замовлень і bigint із бази даних усі на це натрапляють, тож тримайте їх рядками від початку до кінця.

Коротко: трактуйте ID-сніжинки, номери замовлень і первинні ключі bigint як рядки під час серіалізації, передачі й розбору; тримайте числовий тип, лише коли доведено, що поле залишається в межах 2^53-1, і тоді забезпечуйте це обмеженням maximum у Schema.
Бо пошкодження відбувається під час розбору, мовчки.
JSON.parse('{"id": 9007199254740993}');
// { id: 9007199254740992 }
Пропущена цифра зникла; відновити нічого.
Звідки береться 2^53
У JavaScript один числовий тип: подвійна точність IEEE 754 з 53 бітами значущості (один неявний, 52 явні). Точні цілі тому закінчуються на ±(2^53-1):
-9007199254740991 .. 9007199254740991
Вище цієї межі сусідні подавані значення різняться на 2, 4, 8. 9007199254740993 непарне, а на такій величині кожне подаване значення парне, тож воно округлюється до 9007199254740992.
Які поля страждають
| Поле | Цифри | Ризик |
|---|---|---|
| ID-сніжинки | 18–19 | Високий, останні цифри змінюються |
| Номери замовлень / транзакцій | 16–20 | Високий |
| Первинні ключі bigint бази даних | 19 | Високий |
| Гроші в дрібних одиницях | — | Низький |
| Часові мітки в мілісекундах | 13 | Безпечно |
Спільна нитка: це ідентифікатори, а не кількості. Ідентифікатор передають, його ніколи не додають і не множать, тож рядок — правильний тип.
Чотири виправлення
Серіалізуйте як рядок. Бекенд конвертує поля bigint перед записом відповіді. Просто й довговічно; документуйте, що ці поля — рядки.
Дозвольте серіалізатору це зробити. ToStringSerializer для Long у Jackson, тег json:",string" у Go. Та сама ідея: тримайте значення поза числовим шляхом.
Розбирайте з підтримкою BigInt. json-bigint і подібні бібліотеки. Зауважте, що BigInt не можна змішувати з арифметикою Number, а JSON.stringify його відхиляє.
Обмежте діапазон. Якщо поле справді не може перевищувати 2^53, додайте maximum у Schema, щоб позадіапазонні дані провалювали перевірку, а не зазнавали збою мовчки в браузері.
Як визначити, чи у вас цей баг
Візьміть відомий ID і проженіть його туди-назад: серіалізуйте, передайте, розберіть, порівняйте символ за символом. Або огляньте сире тіло відповіді — 19-значне число без лапок, що приходить із неправильними останніми цифрами, майже напевно є втратою під час розбору.
Часті питання
Чому JSON.parse змінює останні цифри без помилки?
У JavaScript один числовий тип — подвійна точність IEEE 754, тож цілі числа понад 2^53-1 можуть потрапити лише на найближче подаване значення. JSON.parse трактує це як звичайне числове перетворення: він ні не кидає виняток, ні не попереджає.
Як фронтенд може зберегти великі цілі цілими?
Використовуйте бібліотеку на кшталт json-bigint, бо JSON.parse цього не може. Ціна — BigInt не можна змішувати з арифметикою Number, а JSON.stringify його відхиляє, тож оточуючий код теж має змінитися.
Чи має документація API заявляти, що ці поля — рядки?
Так. Серіалізація в рядки — найпростіше й найдовговічніше виправлення, але воно працює, лише якщо споживачі трактують значення як текст, тож тип поля треба записати в контракт API.