Повторяющиеся ключи JSON: как обнаружить конфликт до потери значения

Отличаем повтор ключа от повторных записей, обнаруживаем совпадения после Unicode escaping и выбираем исправление по контракту источника.

Два одинаковых ключа у одного отверстия как образ повторяющихся ключей JSON

Документ содержит два значения mode, но после обычного разбора осталось одно. Если анализировать уже готовый объект, конфликт может быть невидим. Поэтому повторяющиеся ключи ищут в исходном JSON-тексте до преобразования в словарь или объект приложения.

Речь о ключах внутри одного объекта, а не о двух объектах массива с одинаковым id. Это разные проверки. RFC рекомендует уникальные имена и предупреждает, что обработка повторов у получателей различается. RFC 8259, раздел 4.

Пример неоднозначного документа

Все значения синтетические:

{"mode":"safe","mode":"fast"}

Какой режим намеревался выбрать источник, из текста неизвестно. Сохранить последнее значение — техническая стратегия, но не доказательство правильности. Сохранить первое тоже не решает вопрос намерения. Для режима безопасности или доступа такой выбор особенно нежелательно делать автоматически.

В синтаксическом валидаторе Neraviko этот текст проходит проверку грамматики. Форматтер сохраняет оба ключа, потому что работает с токенами исходного текста. Это не означает, что последующий получатель согласится с ними.

Преобразования и режим JSON Schema используют более строгий декодер Neraviko: повтор ключа отклоняется кодом DUPLICATE_JSON_KEY. Не пытайтесь обойти его через минификацию: пробелы не устраняют конфликт.

Проверка в Python до построения словаря

Стандартный модуль Python умеет передать пары объекта в object_pairs_hook. Это позволяет проверить имена до создания итогового словаря. Пример выполняется без внешних пакетов:

import json

def unique_object(pairs):
    result = {}
    for key, value in pairs:
        if key in result:
            raise ValueError(f"Duplicate key: {key}")
        result[key] = value
    return result

samples = [
    '{"mode":"safe","mode":"fast"}',
    r'{"a":1,"\u0061":2}',
    '{"left":{"mode":"safe"},"right":{"mode":"fast"}}',
]
for source in samples:
    try:
        json.loads(source, object_pairs_hook=unique_object)
        print("ok")
    except ValueError as error:
        print(error)

Проверенный вывод:

Duplicate key: mode
Duplicate key: a
ok

Второй случай показывает, что обычное имя a и escape \u0061 совпадают после декодирования. Поиск буквального текста "a" не обнаружил бы обе записи. В третьем случае ключи принадлежат разным вложенным объектам, поэтому конфликта внутри объекта нет. Поведение hook описано в официальной документации Python json.

Этот короткий код показывает принцип, но не выдаёт точную строку и JSON Pointer каждого повтора. Для отчёта по большому документу нужен парсер, сохраняющий координаты токенов. Не восстанавливайте координаты поиском первого вхождения строки: имена могут встретиться в значениях и вложенных объектах.

Почему проверка после JSON.parse запаздывает

Если парсер уже выбрал одно значение, обход свойств построенного объекта видит результат выбора. Исходную вторую пару он не восстановит. Поэтому hook, которому передают готовый объект после разбора, нельзя автоматически считать детектором повторов. Проверяйте, на каком этапе библиотека сообщает имена.

Сохраняйте исходный текст для диагностики в разрешённой среде. Отформатированная копия Neraviko может помочь прочитать конфликт, но выдача объекта обычным сериализатором уже могла скрыть его. Разделяйте сохранение исходника, обнаружение повтора и исправление: одно действие не заменяет два остальных.

Откуда возникают повторы

Частая причина — ручное объединение двух конфигураций: обе стороны содержат mode, а строки просто склеили. Другая причина — генератор сопоставил два исходных поля одному имени. Вложенность усложняет проверку: ключ может быть уникален на верхнем уровне и повторяться внутри одного элемента массива.

Исправьте правило объединения там, где создаётся документ. Если значения должны существовать одновременно, контракт может требовать отдельные имена или массив вариантов. Это изменение модели, поэтому его согласуют с получателем. Удаление одной пары без понимания смысла лишь создаёт видимость чистого файла.

Для регрессии сохраните синтетические случаи: прямой повтор, повтор после escape, одинаковое имя в двух разных объектах и документ без повторов. Проверка должна отвергать первые два и принимать последние. Так новый парсер не превратит отказ в молчаливый выбор значения.

Практическая граница инструмента

Neraviko не заявляет отдельный интерфейс устранения дублей с политикой first-wins или last-wins. Его синтаксическая проверка и строгие операции имеют разные границы, показанные выше. Выбирайте операцию по задаче и читайте конкретный code, path и position ошибки.

Текст отправляется на сервер. Для диагностики достаточно короткого искусственного объекта; рабочие секреты не нужны. JSON-операции ограничены 262 144 байтами запроса и общим лимитом API. Если конфликт обнаружен, результатом работы должна стать однозначная модель данных, а не только успешно выполненный formatter.