Go Decoder.UseNumber: как не округлить большой ID в map[string]any
API получил идентификатор 9007199254740993, а после обработки вернул другое число. При декодировании в interface{} JSON-числа могут стать float64, который не представляет все большие целые точно. Для идентификатора такая потеря меняет сам объект, а не только формат отображения.
Как разобрать ситуацию
Для известной структуры можно декодировать прямо в подходящий целочисленный тип с проверкой диапазона. Проблемный случай этой статьи — динамическая map[string]any: обычный decoder использует float64 для числа. Decoder.UseNumber сохраняет его как json.Number. После этого всё равно нужно проверить диапазон и формат перед арифметикой или обращением к базе.
Пример и ожидаемый результат
Минимальная программа дважды читает объект с числом 9007199254740993 в map[string]any: обычным Unmarshal и Decoder с UseNumber. Первый путь теряет последнюю единицу, второй сохраняет точный числовой текст. Тест использует число за границей целых, безопасно представимых float64.
package main
import (
"encoding/json"
"fmt"
"strings"
)
func main() {
raw := `{"id":9007199254740993}`
var plain map[string]any
if err := json.Unmarshal([]byte(raw), &plain); err != nil {
panic(err)
}
fmt.Printf("%.0f\n", plain["id"].(float64))
var exact map[string]any
d := json.NewDecoder(strings.NewReader(raw))
d.UseNumber()
if err := d.Decode(&exact); err != nil {
panic(err)
}
fmt.Println(exact["id"].(json.Number).String())
}
Ожидаемый вывод — сначала 9007199254740992, затем исходное 9007199254740993. Это конкретное различие двух режимов декодирования Go, проверяемое локально стандартной библиотекой.
Проверка перед применением
- Сравните ввод и вывод как точное целое или строку, а не с округлением.
- Проверьте максимально допустимый ID и значение за его пределом.
- Согласуйте формат с JavaScript-клиентами: исправление только сервера не устраняет потерю на другой стороне.
Добавьте такой ID в контрактные фикстуры интеграции, даже если текущая база пока выдаёт маленькие значения. Ошибка может проявиться при подключении внешнего поставщика или миграции генератора ключей. Проверка одного поля важнее общего равенства размера JSON: повреждённый идентификатор сохраняет и корректный синтаксис, и привычную длину ответа.
Границы решения
UseNumber не выполняет бизнес-валидацию и не превращает дробные значения в подходящие целые. Если контракт требует int64, проверяйте Int64 и возвращаемую ошибку. Для арифметики денег необходимо отдельное согласованное представление.
Если базовый Go уже знаком и хочется связать HTTP, хранение данных и обработку отказов в одну систему, посмотрите наш платный курс
Микросервисы на Go: HTTP, PostgreSQL, gRPC, Kafka и Docker. Перед записью сравните программу со своей задачей и подготовкой.
Техническая справка: pkg.go.dev: документация.
Следующий шаг: Go backend: HTTP, конкурентность и устойчивость сервиса. По смежным вопросам: Как оборачивать ошибки в Go и не потерять машинную проверку причины, Отмена HTTP-запроса в Go: как убедиться, что context дошёл до клиента.
