Инструменты разработчика · JSON форматировщик и валидатор
Имеет ли значение порядок ключей в JSON? Порядок, равенство и RFC 8785
· Фон
JSON стандарты проверка
Спецификация JSON называет объекты неупорядоченными, но реальные парсеры и сериализаторы обычно сохраняют порядок, и от этого зависят схемы подписи. В этом посте рассказывается о том, что говорит спецификация, что делают реализации и как канонизация разрешает противоречие.
Те же данные, разные байты
`{"city":"Oslo","temp":4}` и `{"temp":4,"city":"Oslo"}` содержат одни и те же два имени и значения, однако их исходные байты различаются. Отступы могут добавить гораздо больше текстовых различий без изменения анализируемого значения. Вот почему для «равного JSON» необходимо правило сравнения: сравниваете ли вы текст, разобранные объекты или каноническое представление, определенное другим протоколом?
ToolAcre может удалить шум пробелов, отформатировав оба документа с одинаковым отступом. Он также может рекурсивно сортировать ключи объектов, если выбран этот параметр. Сортировка намеренно меняет порядок элементов, но никогда не перемещает элементы массива, поскольку позиция массива представляет данные. Ни обычное форматирование, ни эта необязательная сортировка не создают RFC 8785 канонического JSON, поэтому выходные данные не должны заменяться указанным форматом подписи.
Что говорит RFC 8259 — объект представляет собой неупорядоченную коллекцию пар name/value, и реализации могут отображать порядок или нет.
RFC 8259 описывает объект как неупорядоченную коллекцию пар name/value. Следовательно, программное обеспечение, которое рассматривает порядок членов как значение обычного объекта JSON, полагается на поведение вне этой абстрактной модели. Массивы явно упорядочены, поэтому `["draft","final"]` не является взаимозаменяемым с `["final","draft"]`. Порядок объектов и порядок массива никогда не должны нормализоваться по одному и тому же правилу.
RFC также отмечает, что библиотеки различаются тем, предоставляют ли они порядок элементов вызывающим объектам. Этого предупреждения достаточно для переносимого дизайна: не кодируйте приоритет или последовательность, помещая один элемент объекта перед другим. Если последовательность имеет значение, представьте ее массивом или явным полем. Форматер, показывающий стабильный порядок, удобен для человека, но он не превращает положение в свойство объекта на уровне стандартов.
Что на самом деле делает этот форматтер
Если сортировка отключена, ToolAcre анализирует документ и сериализует полученное значение JavaScript. Вывод соответствует поведению перечисления свойств JavaScript, а не сохраняет исходный поток токенов байт за байтом. Большинство обычных строковых ключей появляются в знакомом порядке, а имена, подобные целочисленным индексам, могут выводиться перед другими именами. Написание чисел и выбор escape-последовательности также можно нормализовать во время повторной сериализации.
При включенной сортировке форматтер создает новые объекты, собственные ключи которых расположены в алфавитном порядке в каждом вложенном объекте. Для `{"z":{"b":1,"a":2},"items":[{"d":4,"c":3},"x"]}` имена объектов становятся `items`, `z`; имена вложенных объектов также сортируются; и массив все еще содержит свой объект до `"x"`. Сортировка объектов внутри массива не означает сортировку самого массива.
Когда порядок байтов имеет значение
Текстовый порядок имеет значение всякий раз, когда процесс потребляет точные байты, а не абстрактное значение. Хэш файла, ключ кэша, цифровая подпись или построчная разница изменяются при перемещении элементов или изменении пробелов. Это не противоречит неупорядоченной объектной модели; это означает, что окружающий процесс выбрал байтовое представление как часть своих входных данных. Правила представления должны быть явными и общими.
При рутинных проверках последовательные отступы и дополнительная сортировка по алфавиту могут облегчить просмотр изменений. Для криптографической или протокольной работы фраза «выглядит стабильно» не является контрактом. Производитель и верификатор должны использовать точный алгоритм канонизации, требуемый их протоколом, перед хешированием или подписанием. Если алгоритм не указан, не предполагайте, что выходные данные ToolAcre будут соответствовать другому сериализатору в разных версиях, средах выполнения или значениях крайнего случая.
Почему сортировка ключей не RFC 8785
RFC 8785 определяет схему канонизации JSON для создания повторяющихся байтов из совместимых данных. Его работа шире, чем расстановка ключей в алфавитном порядке. Он определяет детерминированную сортировку свойств, а также точное поведение сериализации для строк и чисел и накладывает ограничения на модель ввода. Красивые отступы не являются частью канонического вывода, а сортировка с учетом локали не является приемлемым приближением.
ToolAcre не предъявляет претензий RFC 8785. Его опция сортировки — это функция читаемости, наложенная на `JSON.parse` и `JSON.stringify`; он не проверяет предварительные условия I-JSON и не заменяет правила сериализации RFC. Такое значение, как `1e-7`, ключ, содержащий символы, отличные от ASCII, или экранированную строку, может выявить различия между средством форматирования со случайной сортировкой и соответствующим канонизатором. Используйте проверенную реализацию JCS, когда требуется JCS.
Рабочий пример: справедливое сравнение двух документов
Сравните `{"meta":{"rev":2,"owner":"Mira"},"steps":["cut","pack"]}` с `{"steps":["cut","pack"],"meta":{"owner":"Mira","rev":2}}`. Форматируйте как с двумя пробелами, так и с отключенной сортировкой: пробелы становятся согласованными, но порядок корневых и вложенных элементов по-прежнему может различаться. Проанализируйте оба поля и сравните их предполагаемые поля, чтобы установить эквивалентность на уровне значений, а не объявлять равным необработанный текст.
Включите рекурсивную сортировку ключей, и оба примера будут отображаться с одинаковым порядком объектов, при этом `steps` останется `cut`, а затем `pack`. Это полезно для человеческого сравнения, но это по-прежнему нормализация ToolAcre, а не доказательство RFC 8785. Если бы второй массив был `["pack","cut"]`, ключи сортировки правильно оставили бы эту разницу видимой, поскольку изменение массива изменило бы представленную последовательность.
Что это не распространяется
Сортировка ключей не определяет глубокое равенство для каждого приложения. Повторяющиеся имена принимаются `JSON.parse`, который сохраняет последнее значение, поэтому форматирование может стереть свидетельства того, что один источник содержит повторы. Большие целые числа, возможно, уже потеряли точность значения JavaScript. Домен также может рассматривать выбранные массивы как наборы, но ToolAcre не может вывести это правило и поэтому никогда не меняет порядок массивов.
Средство форматирования также не сравнивает схемы, не применяет значения по умолчанию, не нормализует Unicode и не решает, приемлемы ли два числовых представления для последующей системы. Это отдельные контракты. Используйте форматирование для уменьшения шума представления, специальное структурное сравнение для равенства значений и указанный канонизатор для точных байтов. Смешение этих работ под словом «нормализовать» создает ложную уверенность в том, что на самом деле сравнивалось.
Вывод: порядок незначителен для модели и важен для байтов.
Порядок членов объекта не имеет значения в модели данных RFC 8259, в отличие от порядка массива. Исходные байты по-прежнему записывают как порядок, так и пробелы, поэтому хеши, подписи и текстовые различия учитывают различия, которые могут игнорироваться при сравнении, ориентированном на значение. Прежде чем выбирать инструмент, укажите, какой уровень имеет значение: текстовая идентичность, эквивалентность анализируемого значения и каноническая идентичность, определяемая протоколом, — это три разных вопроса.
ToolAcre поддерживает первые два рабочих процесса лишь косвенно: согласованное форматирование проясняет текстовые различия, а рекурсивная сортировка по алфавитному ключу объекта может сделать сравнения, проводимые человеком, более тихим. Массивы никогда не сортируются. Результат не является RFC 8785 каноническим JSON и не должен подписываться так, как если бы он был. Сохраняйте исходный ввод, когда лексические доказательства имеют значение, особенно потому, что при анализе повторяющихся ключей сохраняется только последнее значение.