Длинный ID из API изменился во фронтенде? Точность JavaScript и передача строк
Изменились последние цифры ID, поиск вернул 404 или номера в счёте слились? Проверьте текст JSON, разбор, хранение и повторную отправку; узнайте, почему String и BigInt иногда уже не помогут, и проверьте передачу строковых ID на локальных примерах.
API вернул длинный числовой идентификатор задачи, но после передачи во фронтенд его последние цифры изменились. Поиск задачи отвечает 404, а две строки счёта могут объединиться в одну. Сначала проверьте, где идентификатор превратился в JavaScript Number. Успешный разбор JSON и HTTP 200 не доказывают, что ID сохранился точно.
Речь идёт о длинных цифровых идентификаторах задач, запросов и записей счёта, а не о правильности чисел, сгенерированных моделью. Не всякий 404 связан с точностью. Примеры используют учебный протокол и локальные данные; платные API не вызываются.
1. Воспроизведите ошибку на двух искусственных ID
Запустите этот фрагмент в Node.js:
const source = '{"task_id":9007199254740993}';
const parsed = JSON.parse(source);
console.log(String(parsed.task_id)); // 9007199254740992
console.log(Number.isSafeInteger(parsed.task_id)); // false
console.log(Number('9007199254740992') ===
Number('9007199254740993')); // true
JavaScript Number использует число с плавающей точкой двойной точности. Верхняя граница безопасных целых Number.MAX_SAFE_INTEGER равна 9007199254740991, то есть 2 в степени 53 минус 1.[1][2] За этой границей меняется не каждое целое, но точное различение всех соседних целых уже не гарантируется. В примере два разных ID становятся равными: это может повредить адрес запроса, ключ Map или результат устранения дублей.
Раздел 6 RFC 8259 допускает ограничения реализации на диапазон и точность чисел JSON и указывает указанный диапазон безопасных целых как область точного взаимодействия распространённых реализаций.[1] Поэтому корректное число в JSON не гарантирует чтение без потерь всеми клиентами. Сам факт передачи большого целого не доказывает нарушение сервером стандарта JSON.
2. Найдите первый участок, где изменилось значение
Сверьтесь с документацией: поле имеет тип JSON string или JSON number? Разрешены ли ведущие нули, буквенный префикс, требуется ли точное сохранение? ID задачи, запроса и модели — разные поля, их нельзя подменять друг другом.
На искусственном примере сравните полный ID и его тип на каждом этапе:
| Этап | Что сравнивать | Типичный признак |
|---|---|---|
| Полученный текст JSON | Есть ли кавычки, каковы исходные последние цифры | Если ошибка уже здесь, проверять предыдущий участок |
| После разбора JSON | typeof, строковое представление, проверку безопасного целого | Текст верен, объект изменился: разбор или числовое преобразование |
| После бизнес-преобразования | Вызовы Number, parseInt, унарного плюса | Адаптер автоматически превратил строку в число |
| Запись и чтение хранилища | Тип поля, тип результата драйвера, прочитанное значение | Проверить хранение и драйвер, если ошибка появляется после записи |
| Повторная сериализация и адрес запроса | Тип в JSON, значение параметра или сегмента пути | В строку преобразуется уже повреждённый ID |
Preview в сетевых инструментах часто показывает уже разобранные данные. Проверяйте контролируемо сохранённый текст ответа или исходное представление: один Preview не доказывает, что сервер передал неверные цифры. Для реальной диагностики сохраняйте только необходимые поля с ограниченным доступом; не публикуйте ключи, полные ответы, клиентские данные и подписанные ссылки.
Даже при точном совпадении ID причиной 404 могут быть аккаунт, окружение, права, путь или срок хранения. Продолжайте проверку исходной задачи по руководству об асинхронных задачах, не создавая новую платную задачу из-за ошибки поиска.
3. Передавайте идентификатор строкой по всей цепочке
Для интерфейса, которым вы управляете, обычно надёжнее такой контракт:
{"task_id":"9007199254740993"}
На стороне формирования ответа создавайте строку из точного значения базы или исходного текста. Клиент, хранилище, форма, очередь и следующий запрос должны передавать её без изменений. Нельзя сначала превратить значение на сервере в небезопасный Number, а затем добавить кавычки: это лишь строковая оболочка уже повреждённых данных.
Тип поля существующего внешнего протокола нельзя менять произвольно. Сначала выясните, есть ли строковое поле или способ чтения без потерь, затем сделайте явный адаптер на контролируемой границе и проверьте всех потребителей. У компонента формы проверьте автоматическое числовое преобразование; для хранилища — реальные типы на входе и выходе драйвера, а не только название столбца.
Строки "0007" и "7" могут обозначать разные ресурсы. Не удаляйте нули, не вызывайте trim и не меняйте регистр ради «нормализации». Эквивалентность задаёт протокол. Суммы и количества токенов, с которыми выполняют вычисления, требуют своих правил точности: рекомендация не означает замену всех чисел строками.
4. Граница Node.js, которая отвергает скрытое преобразование
Сохраните код в task-id.mjs. Учебный контракт требует корневой объект с task_id: строкой из 1–128 цифр ASCII. Ведущие нули допустимы и сохраняются. Формат и длина не являются ограничениями какого-либо провайдера; для UUID и ID с префиксами нужны правила соответствующего протокола.
export function readTaskId(source) {
if (typeof source !== 'string') throw new TypeError('TEXT_REQUIRED');
if (Buffer.byteLength(source, 'utf8') > 65536) {
throw new RangeError('BODY_TOO_LARGE');
}
let data;
try { data = JSON.parse(source); }
catch { throw new Error('INVALID_JSON'); }
if (!data || typeof data !== 'object' || Array.isArray(data)) {
throw new Error('OBJECT_REQUIRED');
}
if (!Object.hasOwn(data, 'task_id')) throw new Error('MISSING_ID');
const id = data.task_id;
if (typeof id !== 'string' || id.length < 1 || id.length > 128 || /[^0-9]/.test(id)) {
throw new Error('STRING_ID_REQUIRED');
}
return id;
}
Из другого файла .mjs в той же папке вызовите:
import { readTaskId } from './task-id.mjs';
const id = readTaskId('{"task_id":"0009007199254740993"}');
console.log(JSON.stringify({ task_id: id }));
// {"task_id":"0009007199254740993"}
STRING_ID_REQUIRED означает нарушение строкового контракта. Функция отклоняет даже безопасное небольшое число 7 и не скрывает изменение типа вызовом String(value). Вызывающий код должен обработать исключение как ошибку протокола, сохранить обезличенные данные и остановить текущий поиск или запись, не создавая задачу заново.
Предел уже полученного текста — 65 536 байт UTF-8. Проверяются корневой тип, наличие поля и формат ID; возвращается только идентификатор, остальные поля не обрабатываются. Функция не реализует HTTP, ограничение потока при приёме, авторизацию или запись в базу. Слой приёма должен отдельно ограничить размер до полной загрузки, проверить статус и тип содержимого. Дублирующиеся ключи JSON функция не обнаруживает; это не универсальный строгий валидатор. Регулярное выражение проверяет только одну уже разобранную строку и не переписывает исходный JSON.
5. Почему добавить BigInt иногда уже поздно
Документация MDN по JSON.parse прямо указывает: reviver вызывается после разбора, когда число могло потерять точность.[3] Поэтому String(parsed.task_id), BigInt(parsed.task_id) и BigInt(value) в обычном reviver не возвращают округлённые исходные цифры.
BigInt('9007199254740993') сохраняет целое, если исходная строка точна. Но обратное преобразование BigInt в Number снова может потерять точность. Кроме того, BigInt удаляет ведущие нули идентификатора, а JSON.stringify по умолчанию выдаёт TypeError при встрече BigInt.[4] Для передачи идентичности проще сохранить строку. Если нужна целочисленная арифметика, сериализация после вычисления всё равно должна соблюдать контракт получателя.
Если upstream возвращает только JSON number, решение должно начинаться с исходного текста JSON: используйте проверенный разбор без потерь либо, в поддерживаемом окружении, context.source внутри reviver JSON.parse для нужного поля. Это свойство даёт исходный текст значения, но поддержку среды необходимо проверить; также нужно определить, допустимы ли экспоненциальная запись, дробная часть и знак.[3] Пример статьи не реализует этот совместимый вариант. Простая обёртка над обычным JSON.parse не делает его разбором без потерь.
Не добавляйте кавычки ко всем числам JSON регулярным выражением: можно повредить текст внутри строк, суммы и вложенные структуры. Если остался только небезопасный Number без точного оригинала, восстанавливайте ID по исходному архиву, записи платформы или документированному запросу только для чтения. Не угадывайте последние цифры, не исправляйте массово исторические ID и не отправляйте задачу заново.
6. Проверьте исправление локальным циклом преобразований
Включите значения по обе стороны границы безопасных целых, соседние длинные ID, ведущие нули, пустое значение, отсутствующее поле, неверный тип, слишком длинный ID и границы размера тела. Основное условие: строковый ID из исходного текста после разбора, бизнес-обработки, JSON.stringify и повторного разбора должен совпасть посимвольно.
Локальные проверки на Node.js подтвердили коллизию двух длинных чисел в Number, невозможность восстановления после преобразования, сохранение строк при повторной сериализации, разделение разных ID в имитации поиска через Map и отказ на некорректном вводе. Функция в обеих языковых версиях посимвольно сверена с проверенным исходником. Реальный драйвер базы, браузерная форма, развёрнутый шлюз и биллинг провайдера не тестировались; эти границы нужно проверить в собственной тестовой среде.
После исправления не заменяйте сразу все исторические ID. Сначала подтвердите соответствие по доверенному источнику, разделите исходный ID, повреждённое локальное значение и доказательство восстановления, затем определите отдельное исправление и откат. Если два номера в счёте слились в один, это не основание автоматически удалить расход: продолжайте сверку выгрузки счёта по запросам.
Для обращения в поддержку подготовьте время с часовым поясом, контракт поля, версию клиента, искусственный пример и первый участок изменения. Используйте шаблон минимального воспроизведения и обезличенного обращения, не отправляйте полный ключ или снимки ответа с личными данными.
7. Источники и границы применимости
- [1] RFC 8259, раздел 6 Numbers: точность чисел JSON и границы совместимости.
- [2] MDN: Number.MAX_SAFE_INTEGER: безопасный диапазон и сравнение соседних целых.
- [3] MDN: JSON.parse: момент вызова reviver и context.source.
- [4] MDN: BigInt: преобразования типов и ограничения сериализации JSON.
Источники проверены 2026-11-12. Статья описывает сбой точности при передаче идентификаторов, не доказывает его наличие у конкретной платформы и не заменяет контракт целевого интерфейса.