В исходном JSON поле score указано дважды: сначала со значением 1, затем со значением 2. При обычном вызове json.loads() из стандартного модуля Python в словаре останется только последнее значение. Успешный разбор не означает, что сохранились обе пары из исходного текста. Чтобы проверить имена заранее, обработайте пары объекта до того, как Python соберёт из них обычный словарь.

Ниже приведены четыре вымышленных JSON-текста. Значения 1 и 2 нужны только для примера и не относятся к реальным показателям исследования. Названия before и after автор примера присвоил сам. Нельзя по ним решать, как переименовать неизвестные повторяющиеся поля в настоящем файле.

Четыре разных случая

Учебный текст   Что проверяется   
repeated   В одном объекте два имени score со значениями 1 и 2   
distinct   Разные имена score_before и score_after   
nested   Два имени score внутри вложенного объекта meta   
separate_objects   Имя score по одному разу в двух отдельных объектах массива   
Авторская схема четырёх вымышленных JSON-текстов: два отклонены за повтор score внутри объекта, два прошли эту узкую проверку. Одинаковое поле в разных объектах массива допустимо в условии. Обычное чтение repeated оставляет score = 2; схема не является интерфейсом или полной проверкой исследовательских данных.
Авторская схема четырёх вымышленных JSON-текстов: два отклонены за повтор score внутри объекта, два прошли эту узкую проверку. Одинаковое поле в разных объектах массива допустимо в условии. Обычное чтение repeated оставляет score = 2; схема не является интерфейсом или полной проверкой исследовательских данных.

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

В документации стандартного модуля json описано поведение повторных имён: по умолчанию сохраняется последняя пара с данным именем. Параметр object_pairs_hook позволяет обработать упорядоченный список пар каждого декодируемого объекта. Проверка ниже относится именно к этому модулю и режиму, а не к любому JSON-парсеру.

Полный код проверки до словаря

import json

samples = {
    "repeated": '{"id":"A01","score":1,"score":2}',
    "distinct": '{"id":"A01","score_before":1,"score_after":2}',
    "nested": '{"id":"A01","meta":{"score":1,"score":2}}',
    "separate_objects": '[{"score":1},{"score":2}]',
}

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

results = {}
for name, text in samples.items():
    try:
        data = json.loads(text, object_pairs_hook=unique_object)
    except ValueError as error:
        results[name] = "REJECT"
        print(name, "REJECT", str(error))
    else:
        results[name] = data
        print(name, "PASS", json.dumps(data, sort_keys=True))

print("default_score", json.loads(samples["repeated"])["score"])

Полученный вывод:

repeated REJECT duplicate key: score
distinct PASS {"id": "A01", "score_after": 2, "score_before": 1}
nested REJECT duplicate key: score
separate_objects PASS [{"score": 1}, {"score": 2}]
default_score 2

Два текста отклонены из-за повторения score, а два прошли эту проверку. В separate_objects одинаковое имя встречается в разных объектах, поэтому оба значения сохраняются. Проверка охватывает и вложенный объект meta: функция вызывается для каждого объекта, а не только для верхнего уровня.

Чего нельзя восстановить из готового словаря

Для repeated обычный разбор оставляет score = 2. По готовому словарю уже нельзя определить, что в исходном тексте перед этим было score = 1. Поэтому сохраняйте исходный JSON до преобразования: после обычного json.loads() проверка словаря не обнаружит потерянную пару.

Функция unique_object создаёт отдельный локальный словарь для каждого объекта. Если имя повторяется, она вызывает исключение, и вызывающий код не принимает документ целиком. В примере ValueError означает отказ для этого текста. В реальном журнале обработки полезно записывать имя файла, место ошибки и её причину. Отдельно проверьте синтаксис, типы и смысл полей.

Не назначайте смысл полям автоматически

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

После проверки имён остаётся отдельная задача: различить отсутствующее поле, null, false, ноль и пустой текст. Она не заменяет контроль повторов: в готовом словаре часть пар уже могла исчезнуть. Похожая опасность есть у повторяющихся заголовков CSV перед DictReader, но там проверяют строку заголовков и позиции столбцов; здесь — пары каждого JSON-объекта, включая вложенные.

Законченный вывод по набору

Результат   Число текстов   
Отклонены repeated и nested   2   
Прошли проверку distinct и separate_objects   2   
Всего проверено   4   

Для вымышленного набора repeated и nested отклонены, а distinct и separate_objects прошли проверку уникальности имён внутри объекта. Это ещё не полная проверка исследовательских данных: отдельно нужны схема, проверка типов и допустимых значений, а также выяснение источника. Код не определяет смысл двух неизвестных полей.

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