问题:如何以相同的顺序比较两个具有相同元素的JSON对象相等?
我如何测试python中两个JSON对象是否相等,而忽略列表的顺序?
例如 …
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"
列表的顺序不同。
回答 0
如果您希望两个具有相同元素但顺序不同的对象相等,那么比较明显的事情就是比较它们的排序后的副本-例如,以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()
除“一个可迭代的。
为了解决这个问题,我们可以定义一个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
回答 1
另一种方法是使用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
这适用于嵌套字典和列表。
回答 2
对其进行解码,并将其作为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的答案。
回答 3
对于以下两个字典“ 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
给我错误的结果即错误。
所以我这样创建了自己的cutstom 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
这给了我正确的预期输出!
逻辑很简单:
如果对象的类型为“列表”,则将第一个列表的每个项目与第二个列表的项目进行比较,直到找到为止;如果在通过第二个列表之后未找到该项目,则“找到”为= false。返回“找到的”值
否则,如果要比较的对象的类型为“ dict”,则比较两个对象中所有相应键的存在值。(执行递归比较)
否则,只需调用obj1 == obj2即可。默认情况下,它适用于字符串和数字的对象,并且eq()的定义适当。
(请注意,可以通过删除在object2中找到的项目来进一步改进该算法,以便object1的下一个项目不会将自身与object2中已经找到的项目进行比较。)
回答 4
您可以编写自己的equals函数:
- 在以下情况下,字典是相等的: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
对于其他想要调试两个JSON对象(通常有一个引用和一个target)的人,可以使用以下解决方案。它将列出从目标到引用的不同/不匹配路径的“ 路径 ”。
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