如何将两个JSON对象与不同顺序的相同元素进行比较?

80 python django comparison json

如何在python中测试两个JSON对象是否相等,忽略列表的顺序?

例如 ...

JSON文档a:

{
    "errors": [
        {"error": "invalid", "field": "email"},
        {"error": "required", "field": "name"}
    ],
    "success": false
}
Run Code Online (Sandbox Code Playgroud)

JSON文件b:

{
    "success": false,
    "errors": [
        {"error": "required", "field": "name"},
        {"error": "invalid", "field": "email"}
    ]
}
Run Code Online (Sandbox Code Playgroud)

a并且b应该比较相等,即使"errors"列表的顺序不同.

Zer*_*eus 114

如果你想要两个具有相同元素但顺序不同的对象进行比较,那么显而易见的事情就是比较它们的排序副本 - 例如,对于由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"}
    ]
}
""")
Run Code Online (Sandbox Code Playgroud)
>>> sorted(a.items()) == sorted(b.items())
False
Run Code Online (Sandbox Code Playgroud)

...但是这不起作用,因为在每种情况下,"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
Run Code Online (Sandbox Code Playgroud)

如果我们将此函数应用于ab,结果比较相等:

>>> ordered(a) == ordered(b)
True
Run Code Online (Sandbox Code Playgroud)

  • @HoussamHsm 当您第一次提到无法排序的字典问题时,我打算修复它以与 Python 3.x 一起使用,但不知何故它离开了我。它现在适用于 2.x 和 3.x :-) (3认同)

stp*_*tpk 32

另一种方法是使用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
Run Code Online (Sandbox Code Playgroud)

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

  • @Danil,可能不应该.列表是一个有序的结构,如果它们只是按顺序不同,我们应该考虑它们的不同.也许对于您的用例,订单无关紧要,但我们不应该假设. (4认同)
  • @stpk 考虑到列表是有序结构,并不意味着没有任务来检查两个列表是否包含相同的元素,无论它们的顺序如何。同样的事情也适用于字典,也就是问题 (4认同)
  • 如果您有列表,这将不起作用。例如`json.dumps({'foo':[3,1,2]},sort_keys = True)== json.dumps({'foo':[2,1,3]},sort_keys = True)` (2认同)

fal*_*tru 15

解码它们并将它们作为mgilson评论进行比较.

只要键和值匹配,顺序对字典无关紧要.(字典在Python中没有顺序)

>>> {'a': 1, 'b': 2} == {'b': 2, 'a': 1}
True
Run Code Online (Sandbox Code Playgroud)

但是顺序在列表中很重要; 排序将解决列表的问题.

>>> [1, 2] == [2, 1]
False
>>> [1, 2] == sorted([2, 1])
True
Run Code Online (Sandbox Code Playgroud)
>>> 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
Run Code Online (Sandbox Code Playgroud)

以上示例将适用于问题中的JSON.有关一般解决方案,请参阅Zero Piraeus的答案.


egg*_*cat 9

更新:请参阅https://eggachecat.github.io/jycm-json-diff-viewer/进行现场演示!现在它有一个 JS 原生实现。

隶属关系:我是该库的作者。

是的!你可以使用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
Run Code Online (Sandbox Code Playgroud)

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

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}
    ]
}
Run Code Online (Sandbox Code Playgroud)

其结果可以呈现为 在此输入图像描述