django 如何比较两个JSON对象中具有相同元素但顺序不同的相等对象?

o75abkj4  于 2022-11-26  发布在  Go
关注(0)|答案(9)|浏览(223)

如何在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"}
    ]
}

即使"errors"列表的顺序不同,ab也应该比较为相等。

qyuhtwio

qyuhtwio1#

如果希望元素相同但顺序不同的两个对象比较起来是相等的,那么很明显要做的就是比较它们的排序副本--例如,对于JSON字符串ab表示的字典:
第一个
...但这是行不通的,因为在每种情况下,顶层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
zbwhf8kr

zbwhf8kr2#

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

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

cwdobuhd

cwdobuhd3#

解码它们,并比较它们作为mgilson评论。
只要键和值匹配,字典的顺序并不重要。(Python中字典没有顺序)

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

但顺序在列表中很重要;排序将解决列表的问题。
第一次
上面的例子适用于问题中的JSON。关于通用的解决方案,请参见Zero比雷埃夫斯的答案。

g6ll5ycj

g6ll5ycj4#

是的!您可以使用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}
    ]
}

其结果可以被呈现为x1c 0d1x

wnavrhmk

wnavrhmk5#

对于以下两个字典'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

这给了我正确的预期输出!
逻辑很简单:
如果对象的类型为'list',则将第一个列表中的每个项与第二个列表中的项进行比较,直到找到为止,如果在遍历第二个列表后没有找到项,则'found'将为= false。返回'found'值
否则,如果要比较的对象是“dict”类型,则比较两个对象中所有相应键的值。(执行递归比较)
否则,只需调用obj 1 == obj 2。默认情况下,它可以很好地处理字符串和数字对象,并且对这些对象适当地定义了eq()。
(Note该算法可以通过移除在object 2中找到的项来进一步改进,使得object 1的下一项将不将其自身与已经在object 2中找到的项进行比较)

af7jpaap

af7jpaap6#

您可以编写自己的equals函数:

  • 如果满足以下条件,则dict相等:1)所有键相等,2)所有值相等
  • 如果满足以下条件,则列表相等:所有项目都相等且顺序相同
  • 如果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
o7jaxewo

o7jaxewo7#

对于其他想要调试两个JSON对象(通常有一个reference和一个target)的人,这里有一个你可以使用的解决方案。它将列出从target到reference的不同/不匹配对象的“path”。
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
nfeuvbwi

nfeuvbwi8#

导入json

API响应示例

一些JSON:

x = '{“姓名”:“约翰”,“年龄”:30,“城市”:“纽约”}'

parse x json to Python字典:

y = json.载荷(x)

访问Python字典打印(y[“age”])

需要json作为字典

thisdict = {“姓名”:“约翰”,“年龄”:30,“城市”:“纽约”}打印(thisdict)

访问Python字典

印刷品

比较两个访问Python字典

如果此命令== y:print(“dict 1等于dict 2”)否则:打印(“dict 1不等于dict 2”)

pgky5nke

pgky5nke9#

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

相关问题