Как сравнить два объекта 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"
отличается.
Ответы
Ответ 1
Если вы хотите, чтобы два объекта с одинаковыми элементами, но в другом порядке для сравнения равны, то очевидная вещь - сравнить отсортированные копии их - например, для словарей, представленных вашими строками 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"
диктатора верхнего уровня представляет собой список с теми же элементами в другом порядке, а sorted()
не пытается сортировать что угодно, кроме "верхнего" уровня итерации.
Чтобы исправить это, мы можем определить функцию 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
Ответ 2
Другим способом может быть использование опции 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
Это работает для вложенных словарей и списков.
Ответ 3
Декодируйте их и сравните их как комментарий в 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.
Ответ 4
Вы можете написать свою собственную функцию равенства:
- Дикты равны, если: 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
Ответ 5
Для следующих двух диктов '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)