如何比较两个 JSON 对象与相同的元素在不同的顺序相等?

如何在不考虑列表顺序的情况下测试两个 JSON 对象在 python 中是否相等?

比如说..。

JSON 文档 :

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

JSON 文档 B:

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

尽管 "errors"列表的顺序不同,但 ab应该相等。

224858 次浏览

解码它们,然后把它们作为 Mgilson 评论进行比较。

对于 Dictionary 来说,只要键和值匹配,顺序并不重要(Dictionary 在 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 的答案。

如果你想要两个元素相同但顺序不同的对象比较相等,那么最明显的事情就是比较它们的排序副本-例如,对于由 JSON 字符串 ab表示的字典:

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

... ... 但是这不起作用,因为在每种情况下,顶级 dict 的 "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

如果我们将这个函数应用于 ab,结果相同:

>>> ordered(a) == ordered(b)
True

另一种方法是使用 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

这适用于嵌套字典和列表。

可以编写自己的 equals 函数:

  • 如果: 1)所有的键都相等,2)所有的值都相等,则 dicts 是相等的
  • 列表是相等的如果: 所有项目是相等的,在同样的顺序
  • 基元是相等的,如果 a == b

因为处理的是 json,所以会有标准的 python 类型: dictlist等等,所以可以执行硬类型检查 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

对于下面的两个字母“ 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

给了我错误的结果。

因此,我创建了自己的自定义 ObjectCompator,如下所示:

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

这给了我正确的预期输出!

逻辑很简单:

如果对象的类型是“ list”,那么将第一个列表中的每个项目与第二个列表中的项目进行比较,直到找到为止。如果在遍历第二个列表后没有找到该项目,那么“ found”将为 = false。返回“ found”值

否则,如果要比较的对象的类型为“ dict”,则比较两个对象中所有相应键的值。(执行递归比较)

否则,只需调用 objec1 = = objec2。默认情况下,对于字符串和数字的对象,以及对于那些定义适当的 等式() ,它可以正常工作。

(注意,可以通过删除在 object2中找到的项目来进一步改进算法,这样 object1的下一个项目就不会与在 object2中已经找到的项目进行比较)

对于其他想要调试这两个 JSON 对象(通常有一个 参考文献和一个 目标)的人,这里有一个您可以使用的解决方案。它将列出从目标到引用的不同/不匹配的“ 路径”。

level选项用于选择您希望查看的深度。

可以打开 show_variables选项以显示相关变量。

def compareJson(example_json, target_json, level=-1, show_variables=False):
_different_variables = _parseJSON(example_json, target_json, level=level, show_variables=show_variables)
return len(_different_variables) == 0, _different_variables


def _parseJSON(reference, target, path=[], level=-1, show_variables=False):
if level > 0 and len(path) == level:
return []
  

_different_variables = list()
# the case that the inputs is a dict (i.e. json dict)
if isinstance(reference, dict):
for _key in reference:
_path = path+[_key]
try:
_different_variables += _parseJSON(reference[_key], target[_key], _path, level, show_variables)
except KeyError:
_record = ''.join(['[%s]'%str(p) for p in _path])
if show_variables:
_record += ': %s <--> MISSING!!'%str(reference[_key])
_different_variables.append(_record)
# the case that the inputs is a list/tuple
elif isinstance(reference, list) or isinstance(reference, tuple):
for index, v in enumerate(reference):
_path = path+[index]
try:
_target_v = target[index]
_different_variables += _parseJSON(v, _target_v, _path, level, show_variables)
except IndexError:
_record = ''.join(['[%s]'%str(p) for p in _path])
if show_variables:
_record += ': %s <--> MISSING!!'%str(v)
_different_variables.append(_record)
# the actual comparison about the value, if they are not the same, record it
elif reference != target:
_record = ''.join(['[%s]'%str(p) for p in path])
if show_variables:
_record += ': %s <--> %s'%(str(reference), str(target))
_different_variables.append(_record)


return _different_variables

使用 KnoDL,它可以在不使用映射字段的情况下匹配数据。

是的! 你可以使用 Jycm

from jycm.helper import make_ignore_order_func
from jycm.jycm import YouchamaJsonDiffer


a = {
"errors": [
{"error": "invalid", "field": "email"},
{"error": "required", "field": "name"}
],
"success": False
}
b = {
"success": False,
"errors": [
{"error": "required", "field": "name"},
{"error": "invalid", "field": "email"}
]
}
ycm = YouchamaJsonDiffer(a, b, ignore_order_func=make_ignore_order_func([
"^errors",
]))
ycm.diff()
assert ycm.to_dict(no_pairs=True) == {} # aka no diff

对于更复杂的示例(深层结构中的值更改)

from jycm.helper import make_ignore_order_func
from jycm.jycm import YouchamaJsonDiffer


a = {
"errors": [
{"error": "invalid", "field": "email"},
{"error": "required", "field": "name"}
],
"success": True
}


b = {
"success": False,
"errors": [
{"error": "required", "field": "name-1"},
{"error": "invalid", "field": "email"}
]
}
ycm = YouchamaJsonDiffer(a, b, ignore_order_func=make_ignore_order_func([
"^errors",
]))
ycm.diff()
assert ycm.to_dict() == {
'just4vis:pairs': [
{'left': 'invalid', 'right': 'invalid', 'left_path': 'errors->[0]->error', 'right_path': 'errors->[1]->error'},
{'left': {'error': 'invalid', 'field': 'email'}, 'right': {'error': 'invalid', 'field': 'email'},
'left_path': 'errors->[0]', 'right_path': 'errors->[1]'},
{'left': 'email', 'right': 'email', 'left_path': 'errors->[0]->field', 'right_path': 'errors->[1]->field'},
{'left': {'error': 'invalid', 'field': 'email'}, 'right': {'error': 'invalid', 'field': 'email'},
'left_path': 'errors->[0]', 'right_path': 'errors->[1]'},
{'left': 'required', 'right': 'required', 'left_path': 'errors->[1]->error',
'right_path': 'errors->[0]->error'},
{'left': {'error': 'required', 'field': 'name'}, 'right': {'error': 'required', 'field': 'name-1'},
'left_path': 'errors->[1]', 'right_path': 'errors->[0]'},
{'left': 'name', 'right': 'name-1', 'left_path': 'errors->[1]->field', 'right_path': 'errors->[0]->field'},
{'left': {'error': 'required', 'field': 'name'}, 'right': {'error': 'required', 'field': 'name-1'},
'left_path': 'errors->[1]', 'right_path': 'errors->[0]'},
{'left': {'error': 'required', 'field': 'name'}, 'right': {'error': 'required', 'field': 'name-1'},
'left_path': 'errors->[1]', 'right_path': 'errors->[0]'}
],
'value_changes': [
{'left': 'name', 'right': 'name-1', 'left_path': 'errors->[1]->field', 'right_path': 'errors->[0]->field',
'old': 'name', 'new': 'name-1'},
{'left': True, 'right': False, 'left_path': 'success', 'right_path': 'success', 'old': True, 'new': False}
]
}

其结果可以呈现为 enter image description here

import json


#API response sample
# some JSON:


x = '{ "name":"John", "age":30, "city":"New York"}'


# parse x json to Python dictionary:
y = json.loads(x)


#access Python dictionary
print(y["age"])




# expected json as dictionary
thisdict = { "name":"John", "age":30, "city":"New York"}
print(thisdict)




# access Python dictionary
print(thisdict["age"])


# Compare Two access Python dictionary


if thisdict == y:
print ("dict1 is equal to dict2")
else:
print ("dict1 is not equal to dict2")