JSON Toolkit

Dlaczego duże liczby całkowite tracą precyzję

Podsumowanie: Liczby całkowite powyżej 2^53-1 tracą precyzję w JSON.parse. Identyfikatory snowflake, numery zamówień i biginty bazodanowe wszystkie na to trafiają, więc trzymaj je jako łańcuchy od początku do końca.

Średniozaawansowany~4 min czytaniaOpublikowano 2026-10-30Zaktualizowano 2026-11-04
Dlaczego duże liczby całkowite tracą precyzję w JSON i jak je zachować

Najważniejsze: traktuj identyfikatory snowflake, numery zamówień i bigintowe klucze główne jako łańcuchy przez serializację, transport i parsowanie; zachowaj typ numeryczny tylko wtedy, gdy pole dowiedziono, że pozostaje w granicach 2^53-1, i wymuś to ograniczeniem maximum w Schema.

Bo uszkodzenie następuje podczas parsowania, po cichu.

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

Brakująca cyfra przepadła; nie ma czego odzyskać.

Skąd bierze się 2^53

JavaScript ma jeden typ liczbowy: podwójną precyzję IEEE 754, z 53 bitami znaczącymi (jeden niejawny, 52 jawne). Dokładne liczby całkowite kończą się więc na ±(2^53-1):

-9007199254740991 .. 9007199254740991

Powyżej tej linii kolejne przedstawialne wartości są oddalone o 2, 4, 8. 9007199254740993 jest nieparzyste, a przy tej wielkości każda przedstawialna wartość jest parzysta, więc zaokrągla się do 9007199254740992.

Pola, które na to trafiają

Pole Cyfry Ryzyko
Identyfikatory snowflake 18–19 Wysokie, ostatnie cyfry się zmieniają
Numery zamówień / transakcji 16–20 Wysokie
Bigintowe klucze główne bazy 19 Wysokie
Pieniądze w jednostkach pomocniczych — Niskie
Znaczniki czasu w milisekundach 13 Bezpieczne

Wspólny wątek: to identyfikatory, a nie ilości. Identyfikator jest transportowany, nigdy dodawany czy mnożony, więc łańcuch jest właściwym typem.

Cztery poprawki

Serializuj jako łańcuch. Backend konwertuje pola bigint przed zapisaniem odpowiedzi. Proste i trwałe; udokumentuj, że te pola są łańcuchami.

Pozwól serializatorowi to obsłużyć. ToStringSerializer Jacksona dla Long, tag json:",string" w Go. Ta sama idea: trzymaj wartość poza ścieżką numeryczną.

Parsuj ze wsparciem BigInt. json-bigint i podobne biblioteki. Uwaga: BigInt nie miesza się z arytmetyką Number, a JSON.stringify je odrzuca.

Ogranicz zakres. Jeśli pole naprawdę nie może przekroczyć 2^53, dodaj maximum w Schema, aby dane poza zakresem zawodzić walidację, zamiast po cichu zawodzić w przeglądarce.

Jak rozpoznać, czy masz ten bug

Weź znany identyfikator i przepuść go w obie strony: serializuj, wyślij, sparsuj, porównaj znak po znaku. Albo zbadaj surowe ciało odpowiedzi — nieujęta w cudzysłowy 19-cyfrowa liczba, która przychodzi ze złymi ostatnimi cyframi, niemal na pewno jest stratą przy parsowaniu.

FAQ

Dlaczego JSON.parse zmienia ostatnie cyfry bez błędu?

JavaScript ma jeden typ liczbowy, podwójną precyzję IEEE 754, więc liczby całkowite powyżej 2^53-1 mogą trafić tylko na najbliższą przedstawialną wartość. JSON.parse traktuje to jako normalną konwersję numeryczną: ani nie rzuca, ani nie ostrzega.

Jak frontend może zachować duże liczby całkowite w nienaruszonym stanie?

Użyj biblioteki takiej jak json-bigint, bo JSON.parse tego nie potrafi. Kosztem jest to, że BigInt nie miesza się z arytmetyką Number, a JSON.stringify je odrzuca, więc otaczający kod też musi się zmienić.

Czy dokumentacja API powinna stwierdzać, że te pola są łańcuchami?

Tak. Serializacja do łańcuchów jest najprostszą i najbardziej trwałą poprawką, ale działa tylko wtedy, gdy konsumenci traktują wartość jako tekst, więc typ pola musi być wpisany w kontrakt API.

Dodatkowe materiały