it-swarm.com.ru

Как сравнить два объекта JSON с одинаковыми элементами в разном порядке?

Как я могу проверить, равны ли два объекта JSON в python, независимо от порядка списков?

Например ...

Документ JSON a :

{
    "errors": [
        {"error": "invalid", "field": "email"},
        {"error": "required", "field": "name"}
    ],
    "success": false
}

Документ JSON b :

{
    "success": false,
    "errors": [
        {"error": "required", "field": "name"},
        {"error": "invalid", "field": "email"}
    ]
}

a и b должны сравниваться одинаково, даже если порядок списков "errors" различен.

61
Houssam Hsm

Если вы хотите, чтобы два объекта с одинаковыми элементами, но в различном порядке, сравнивались одинаково, то очевидное, что нужно сделать, это сравнить отсортированные копии их, например, для словарей, представленных вашими строками JSON a и b:

import json

a = json.loads("""
{
    "errors": [
        {"error": "invalid", "field": "email"},
        {"error": "required", "field": "name"}
    ],
    "success": false
}
""")

b = json.loads("""
{
    "success": false,
    "errors": [
        {"error": "required", "field": "name"},
        {"error": "invalid", "field": "email"}
    ]
}
""")
>>> sorted(a.items()) == sorted(b.items())
False

... но это не работает, потому что в каждом случае элемент "errors" в верхнем уровне dict представляет собой список с одинаковыми элементами в другом порядке, и sorted() не пытается сортировать что-либо, кроме "top" уровень итерируемый.

Чтобы это исправить, мы можем определить функцию ordered, которая будет рекурсивно сортировать любые найденные списки (и преобразовывать словари в списки пар (key, value) так, чтобы их можно было заказать):

def ordered(obj):
    if isinstance(obj, dict):
        return sorted((k, ordered(v)) for k, v in obj.items())
    if isinstance(obj, list):
        return sorted(ordered(x) for x in obj)
    else:
        return obj

Если мы применим эту функцию к a и b, результаты будут равны:

>>> ordered(a) == ordered(b)
True
90
Zero Piraeus

Другим способом может быть использование опции json.dumps(X, sort_keys=True):

import json
a, b = json.dumps(a, sort_keys=True), json.dumps(b, sort_keys=True)
a == b # a normal string comparison

Это работает для вложенных словарей и списков.

20
stpk

Расшифруйте их и сравните с комментариями mgilson.

Порядок не имеет значения для словаря, если ключи и значения совпадают. (Словарь не имеет порядка в Python)

>>> {'a': 1, 'b': 2} == {'b': 2, 'a': 1}
True

Но порядок важен в списке; Сортировка решит проблему для списков.

>>> [1, 2] == [2, 1]
False
>>> [1, 2] == sorted([2, 1])
True

>>> a = '{"errors": [{"error": "invalid", "field": "email"}, {"error": "required", "field": "name"}], "success": false}'
>>> b = '{"errors": [{"error": "required", "field": "name"}, {"error": "invalid", "field": "email"}], "success": false}'
>>> a, b = json.loads(a), json.loads(b)
>>> a['errors'].sort()
>>> b['errors'].sort()
>>> a == b
True

Приведенный выше пример будет работать для JSON в вопросе. Для общего решения см. Ответ Zero Piraeus.

13
falsetru

Для следующих двух диктов 'dictWithListsInValue' и 'reorderedDictWithReorderedListsInValue', которые являются просто переупорядоченными версиями друг друга

dictObj = {"foo": "bar", "john": "doe"}
reorderedDictObj = {"john": "doe", "foo": "bar"}
dictObj2 = {"abc": "def"}
dictWithListsInValue = {'A': [{'X': [dictObj2, dictObj]}, {'Y': 2}], 'B': dictObj2}
reorderedDictWithReorderedListsInValue = {'B': dictObj2, 'A': [{'Y': 2}, {'X': [reorderedDictObj, dictObj2]}]}
a = {"L": "M", "N": dictWithListsInValue}
b = {"L": "M", "N": reorderedDictWithReorderedListsInValue}

print(sorted(a.items()) == sorted(b.items()))  # gives false

дал мне неправильный результат, т.е. ложный.

Таким образом, я создал свой собственный ObjectComparator, как это:

def my_list_cmp(list1, list2):
    if (list1.__len__() != list2.__len__()):
        return False

    for l in list1:
        found = False
        for m in list2:
            res = my_obj_cmp(l, m)
            if (res):
                found = True
                break

        if (not found):
            return False

    return True


def my_obj_cmp(obj1, obj2):
    if isinstance(obj1, list):
        if (not isinstance(obj2, list)):
            return False
        return my_list_cmp(obj1, obj2)
    Elif (isinstance(obj1, dict)):
        if (not isinstance(obj2, dict)):
            return False
        exp = set(obj2.keys()) == set(obj1.keys())
        if (not exp):
            # print(obj1.keys(), obj2.keys())
            return False
        for k in obj1.keys():
            val1 = obj1.get(k)
            val2 = obj2.get(k)
            if isinstance(val1, list):
                if (not my_list_cmp(val1, val2)):
                    return False
            Elif isinstance(val1, dict):
                if (not my_obj_cmp(val1, val2)):
                    return False
            else:
                if val2 != val1:
                    return False
    else:
        return obj1 == obj2

    return True


dictObj = {"foo": "bar", "john": "doe"}
reorderedDictObj = {"john": "doe", "foo": "bar"}
dictObj2 = {"abc": "def"}
dictWithListsInValue = {'A': [{'X': [dictObj2, dictObj]}, {'Y': 2}], 'B': dictObj2}
reorderedDictWithReorderedListsInValue = {'B': dictObj2, 'A': [{'Y': 2}, {'X': [reorderedDictObj, dictObj2]}]}
a = {"L": "M", "N": dictWithListsInValue}
b = {"L": "M", "N": reorderedDictWithReorderedListsInValue}

print(my_obj_cmp(a, b))  # gives true

который дал мне правильный ожидаемый результат!

Логика довольно проста:

Если объекты имеют тип «список», то сравнивают каждый элемент первого списка с элементами второго списка до тех пор, пока он не будет найден, а если элемент не найден после прохождения второго списка, то «найденный» будет = ложным. 'найденное' значение возвращается

Иначе, если сравниваемые объекты имеют тип 'dict', сравните значения, присутствующие для всех соответствующих ключей в обоих объектах. (Выполняется рекурсивное сравнение)

Еще просто позвоните obj1 == obj2. По умолчанию он отлично работает для объекта строк и чисел и для них eq () определяется соответствующим образом.

(Обратите внимание, что алгоритм может быть улучшен путем удаления элементов, найденных в object2, так что следующий элемент object1 не будет сравниваться с элементами, уже найденными в object2)

0
NiksVij

Вы можете написать свою собственную функцию равенства:

  • дикты равны, если: 1) все ключи равны, 2) все значения равны
  • списки равны, если: все элементы равны и в том же порядке
  • примитивы равны, если a == b

Поскольку вы имеете дело с json, у вас будут стандартные типы Python: dict, list и т.д., Поэтому вы можете выполнять жесткую проверку типов if type(obj) == 'dict': и т.д.

Грубый пример (не проверен):

def json_equals(jsonA, jsonB):
    if type(jsonA) != type(jsonB):
        # not equal
        return false
    if type(jsonA) == 'dict':
        if len(jsonA) != len(jsonB):
            return false
        for keyA in jsonA:
            if keyA not in jsonB or not json_equal(jsonA[keyA], jsonB[keyA]):
                return false
    Elif type(jsonA) == 'list':
        if len(jsonA) != len(jsonB):
            return false
        for itemA, itemB in Zip(jsonA, jsonB)
            if not json_equal(itemA, itemB):
                return false
    else:
        return jsonA == jsonB
0
Gordon Bean