Русский

Инструменты разработчика · JSON форматировщик и валидатор

Большие целочисленные идентификаторы в JSON: почему средства форматирования JavaScript могут их округлять

· Почему это важно

JSON рабочий процесс разработчика проверка

Большие целочисленные идентификаторы в JSON: почему средства форматирования JavaScript могут их округлять, проиллюстрированные токенами JSON и точной границей проверки
Оригинальная векторная иллюстрация ToolAcre

JSON допускает целые числа любого размера, но JavaScript представляет числа как 64-битные числа с плавающей запятой, поэтому все, что выше 2^53, может измениться при анализе и повторной сериализации. В этом посте объясняется предел, как обнаружить повреждение и как защитить идентификаторы.

Идентификатор, который изменился на единицу

Идентификатор, измененный на единицу, часто оказывается совершенно действительным JSON. Поместите `{"orderId":9007199254740993}` в JavaScript, и `JSON.parse` вернет число, отображаемое значение которого равно `9007199254740992`. Анализ завершается успешно, поскольку токен соответствует числовой грамматике JSON; повреждение происходит при преобразовании этих десятичных цифр в числовое представление JavaScript. Средство форматирования, которое сериализует проанализированное значение, точно записывает округленное число, а не точный токен, который появился в источнике.

Контраст заметен, когда указаны одни и те же цифры. `JSON.parse("{"orderId":"9007199254740993"}")` возвращает строку `9007199254740993`, сохраняя каждый символ, а `JSON.stringify` выдает эти цифры без изменений внутри кавычек. Вот почему сама по себе проверка синтаксиса не может защитить числовой идентификатор. Сравнивайте входные и выходные данные всякий раз, когда появляются длинные целые числа, и рассматривайте идентификаторы как строки на границе производства, когда арифметика не является частью их значения.

Что RFC 8259 говорит о числах

RFC 8259 определяет написание числа JSON, но не дает каждой реализации числового типа произвольной точности. Грамматика допускает необязательный знак минус, целую часть, а также необязательные части дроби и показателя степени. Он исключает такие удобства, как шестнадцатеричная запись, `NaN` и `Infinity`. Следовательно, `9007199254740993` синтаксически допустимо, даже если обычный потребитель JavaScript не может представить это целое число точно как число.

Рекомендации по совместимости спецификации являются практическим предупреждением: программное обеспечение обычно использует двоичные64 числа IEEE 754, а целые числа в диапазоне от отрицательного `2^53 + 1` до положительного `2^53 - 1` совместимы в смысле точного согласия. Валидатор может правильно принять токен большего размера, в то время как анализатор позже его округляет.

Откуда 2^53

Граница `2^53` обусловлена ​​точностью, доступной в двоичном мантиссе. JavaScript предоставляет наибольшее последовательно представимое целое число как `Number.MAX_SAFE_INTEGER`, то есть `9007199254740991`. При этой величине и ниже соседние целые числа могут быть представлены отчетливо. Выше него расстояние между представимыми значениями увеличивается, поэтому некоторые соседние десятичные целые числа сопоставляются с одним и тем же числом. Среда выполнения не усекает строку; он выбирает ближайшее значение, доступное в этом конечном двоичном формате.

Показательной консольной проверкой является `Number.isSafeInteger(9007199254740993)`, которая является ложной, хотя исходный литерал уже был округлен до того, как функция его получила. Другой — `9007199254740992 === 9007199254740993`, который оценивается как true в JavaScript. Эти примеры касаются точной целочисленной идентичности, а не того, становится ли каждое большее число непригодным для использования.

Как при синтаксическом анализе и повторной сериализации теряются цифры

Форматирование анализа и повторной сериализации состоит из трех этапов: чтение числовых символов, создание значения в памяти, а затем создание новых символов из этого значения. Лексические детали исчезают на среднем этапе. С помощью `{"ticket":9223372036854775807}` `JSON.parse` создает ближайший доступный номер JavaScript; `JSON.stringify` затем выдает `9223372036854776000`. Сериализатор не повреждает сохраненный токен самостоятельно. Ко времени сериализации исходная последовательность цифр больше не присутствует в анализируемом объекте.

Реализация репозитория ToolAcre использует `JSON.parse` и `JSON.stringify`, поэтому это ограничение применяется к его форматированному выводу. Его сканер синтаксиса запускается, чтобы определить стабильную причину и местоположение после сбоя синтаксического анализа; он не заменяет числа JavaScript представлением произвольной точности. Таким образом, успешный результат проверки устанавливает грамматику, а разница форматирования может выявить потерю точности.

Рабочий пример: сравнение ввода и вывода

Сравните `{"numeric":9007199254740993,"text":"9007199254740993"}` до и после поездки JavaScript туда и обратно. Запуск `JSON.stringify(JSON.parse(source), null, 2)` создает форматированный объект, элементом `numeric` которого является `9007199254740992`, а `text` остается `"9007199254740993"`. Оба члена были действительны на входе и оба остаются действительны на выходе. Только представление в кавычках сохраняет идентификатор именно потому, что он декодируется как символьные данные, а не как число.

Полезный обзор не просто спрашивает, показал ли форматтер зеленый цвет. Найдите в источнике непрерывные последовательности цифр, сравните любые значения, превышающие безопасный диапазон, и определите, представляет ли каждое поле количество или непрозрачную метку. Если производитель контролирует контракт, измените там метку на строку и задокументируйте этот выбор для потребителей.

Защита идентификаторов в источнике

Защитите идентификаторы в источнике, определяя их как строки в схеме и сериализуя их как строки до того, как любой клиент JavaScript получит полезную нагрузку. Идентификатор может содержать только цифры и при этом не иметь числового значения: сложение, округление и упорядочивание по величине не являются законными операциями с ключом учетной записи. Строка также сохраняет ведущие нули, которые в числовом представлении будут отброшены, даже если ее величина находится в безопасном диапазоне.

Не делайте вывод о межъязыковой безопасности на основании того факта, что другая среда выполнения может хранить большее целое число. Синтаксические анализаторы и целевые типы различаются, и посредник, написанный в JavaScript, может округлять значение до того, как его увидит более поздняя служба. Некоторые специализированные анализаторы сохраняют числовые токены или создают большие целые числа, но каждый участник должен использовать этот контракт.

Что это не распространяется

Чего это не касается, так это более широкой конструкции десятичной арифметики. Такие значения, как `0.1`, имеют собственное поведение с двоичными числами с плавающей запятой, и для денег могут потребоваться масштабированные целые или десятичные типы в соответствии с контрактом приложения. Заключение каждого числа в кавычки не улучшает схему автоматически. Подсчеты, координаты и измерения часто имеют законный числовой характер. Решение зависит от того, должно ли точное написание десятичной дроби или точное целочисленное значение сохраняться для каждого потребителя на пути к данным.

В этом обсуждении также не утверждается, что JSON сам округляет токен или что все анализаторы ведут себя как JavaScript. Свидетельство конкретного репозитория уже: этот форматировщик вызывает `JSON.parse` и `JSON.stringify`, поэтому семантика чисел JavaScript здесь управляет значениями без кавычек. Библиотека JSON произвольной точности может делать разные варианты, но она должна определять, как значения предоставляются и сериализуются.

Вывод: числа выше 2^53 принадлежат строкам.

Вывод конкретен: целочисленные идентификаторы за пределами безопасного диапазона JavaScript принадлежат строкам, когда они должны пройти через JavaScript без изменений. `9007199254740993` как номер JSON является допустимым синтаксисом, но становится `9007199254740992` после `JSON.parse`; `"9007199254740993"` остается точным. Цитаты не являются украшением. Они выбирают представление, которое сохраняет цифры как данные и не позволяет потребителям рассматривать непрозрачную этикетку как приблизительное количество.

Прежде чем заменять документ выходными данными форматтера, сравните длинные числа с оригиналом и исследуйте каждую измененную цифру. По возможности исправьте производителя и схему, чтобы все последующие клиенты постоянно получали безопасную форму. ToolAcre может раскрыть последствия, поскольку его выходные данные отражают проанализированное значение JavaScript, но он не может восстановить цифры, уже потерянные во время анализа.