Làm thế nào để so sánh hai đối tượng JSON với các phần tử giống nhau theo thứ tự khác nhau bằng nhau?


100

Làm cách nào để kiểm tra xem hai đối tượng JSON có bằng nhau trong python hay không, bỏ qua thứ tự của danh sách?

Ví dụ ...

Tài liệu JSON a :

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

Tài liệu JSON b :

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

abnên so sánh bằng nhau, mặc dù thứ tự của các "errors"danh sách là khác nhau.



1
Tại sao không chỉ giải mã chúng và so sánh? Hay ý bạn là thứ tự của "Mảng" hoặc listcác phần tử cũng không quan trọng?
mgilson

@ user2085282 Câu hỏi đó có một vấn đề khác đang xảy ra.
user193661 10/12/15

2
Xin hãy tha thứ cho sự ngây thơ của tôi, nhưng tại sao? Các phần tử danh sách có một thứ tự cụ thể vì một lý do.
ATOzTOA

1
Như đã lưu ý trong câu trả lời này, một mảng JSON được sắp xếp để các đối tượng này chứa các mảng có thứ tự sắp xếp khác nhau sẽ không bằng nhau theo nghĩa chặt chẽ. stackoverflow.com/a/7214312/18891
Eric Ness

Câu trả lời:


142

Nếu bạn muốn hai đối tượng có cùng phần tử nhưng theo thứ tự khác nhau để so sánh bằng nhau, thì điều hiển nhiên cần làm là so sánh các bản sao đã được sắp xếp của chúng - ví dụ: đối với các từ điển được đại diện bởi chuỗi JSON của bạn 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

... nhưng điều đó không hiệu quả, bởi vì trong mỗi trường hợp, "errors"mục của dict cấp cao nhất là danh sách có các phần tử giống nhau theo một thứ tự khác nhau và sorted()không cố gắng sắp xếp bất kỳ thứ gì ngoại trừ cấp "cao nhất" của có thể lặp lại.

Để khắc phục điều đó, chúng ta có thể xác định một orderedhàm sẽ sắp xếp đệ quy bất kỳ danh sách nào mà nó tìm thấy (và chuyển đổi các từ điển thành danh sách các (key, value)cặp để chúng có thể được sắp xếp thứ tự ):

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

Nếu chúng ta áp dụng hàm này cho ab, kết quả so sánh bằng:

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

1
cảm ơn bạn rất nhiều Zero Piraeus. đó chính xác là cách giải quyết chung mà tôi cần. nhưng vấn đề duy nhất là mã chỉ hoạt động cho python 2.x không cho python3. Tôi gặp lỗi sau: TypeError: unorderable type: dict () <dict () Dù sao giải pháp hiện đã rõ ràng. Tôi sẽ cố gắng làm cho nó hoạt động cho python3. Thanks a lot

1
@HoussamHsm Tôi có ý định sửa lỗi này để hoạt động với Python 3.x khi bạn lần đầu tiên đề cập đến vấn đề không thể sửa được, nhưng bằng cách nào đó, nó đã tránh xa tôi. Nó hiện đang làm việc trong cả hai 2.x và 3.x :-)
Zero, Piraeus

khi có một danh sách như thế nào ['astr', {'adict': 'something'}], tôi có TypeErrorkhi cố gắng sắp xếp chúng.
Zhenxiao Hao

1
@ Blairg23, bạn đã hiểu nhầm câu hỏi, đó là về việc so sánh các đối tượng JSON như nhau khi chúng chứa danh sách có các phần tử giống nhau, nhưng theo một thứ tự khác, không phải về bất kỳ thứ tự từ điển nào được cho là.
Zero Piraeus,

1
@ Blairg23 Tôi đồng ý rằng câu hỏi có thể được viết rõ ràng hơn (mặc dù nếu bạn nhìn vào lịch sử chỉnh sửa , nó tốt hơn so với ban đầu). Re: từ điển và thứ tự - vâng, tôi biết ;-)
Zero Piraeus

45

Một cách khác có thể là sử dụng json.dumps(X, sort_keys=True)tùy chọn:

import json
a, b = json.dumps(a, sort_keys=True), json.dumps(b, sort_keys=True)
a == b # a normal string comparison

Điều này hoạt động cho các từ điển và danh sách lồng nhau.


{"error":"a"}, {"error":"b"}vs {"error":"b"}, {"error":"a"} nó sẽ không thể để sắp xếp các trường hợp sau vào trường hợp đầu tiên
Chrome Hearts

@ Blairg23 nhưng bạn sẽ làm gì nếu bạn có danh sách được lồng trong dict? Bạn không thể chỉ so sánh mệnh đề cấp cao nhất và gọi nó là một ngày, đây không phải là câu hỏi này.
stpk

4
Điều này không hoạt động nếu bạn có danh sách bên trong. ví dụ json.dumps({'foo': [3, 1, 2]}, sort_keys=True) == json.dumps({'foo': [2, 1, 3]}, sort_keys=True)
Danil

7
@Danil và có lẽ không nên. Danh sách là một cấu trúc có thứ tự và nếu chúng chỉ khác nhau về thứ tự, chúng ta nên coi chúng là khác nhau. Có thể đối với bạn, đơn đặt hàng không quan trọng, nhưng chúng ta không nên cho rằng điều đó.
stpk 22/02/18

bởi vì danh sách được sắp xếp theo chỉ mục nên chúng sẽ không được sử dụng. [0, 1] không được bằng [1, 0] trong hầu hết các tình huống. Vì vậy, đây là một giải pháp tốt cho trường hợp bình thường, nhưng không phải cho câu hỏi trên. vẫn +1
Harrison

18

Giải mã chúng và so sánh chúng như bình luận của mgilson.

Thứ tự không quan trọng đối với từ điển miễn là các khóa và giá trị khớp. (Từ điển không có thứ tự trong Python)

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

Nhưng thứ tự là quan trọng trong danh sách; sắp xếp sẽ giải quyết vấn đề cho các danh sách.

>>> [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

Ví dụ trên sẽ hoạt động cho JSON trong câu hỏi. Để biết giải pháp chung, hãy xem câu trả lời của Zero Piraeus.


2

Đối với hai phần sau đây 'dictWithListsInValue' và 'reorderedDictWithReorderedListsInValue' chỉ đơn giản là các phiên bản được sắp xếp lại của nhau

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

đã cho tôi kết quả sai tức là sai.

Vì vậy, tôi đã tạo ObjectComparator biểu tượng của riêng mình như thế này:

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

đã cho tôi kết quả mong đợi chính xác!

Logic khá đơn giản:

Nếu các đối tượng thuộc loại 'danh sách' thì hãy so sánh từng mục của danh sách đầu tiên với các mục của danh sách thứ hai cho đến khi tìm thấy và nếu mục không được tìm thấy sau khi xem qua danh sách thứ hai, thì 'tìm thấy' sẽ là = false. giá trị 'tìm thấy' được trả về

Ngược lại, nếu các đối tượng được so sánh thuộc loại 'dict' thì hãy so sánh các giá trị hiện có cho tất cả các khóa tương ứng trong cả hai đối tượng. (So ​​sánh đệ quy được thực hiện)

Khác chỉ cần gọi obj1 == obj2. Theo mặc định, nó hoạt động tốt cho đối tượng là chuỗi và số và đối với những eq () được định nghĩa thích hợp.

(Lưu ý rằng thuật toán có thể được cải thiện hơn nữa bằng cách loại bỏ các mục được tìm thấy trong object2, để mục tiếp theo của object1 sẽ không tự so sánh với các mục đã tìm thấy trong object2)


Bạn có thể vui lòng sửa lỗi thụt lề của mã của bạn không?
colidyre

@colidyre bây giờ thụt lề có ổn không?
NiksVij 30/09/18

Không, vẫn có vấn đề ở đó. Sau phần đầu hàm, khối cũng phải được thụt vào.
colidyre

Đúng. Tôi đã chỉnh sửa lại một lần nữa. Tôi sao chép, dán nó vào IDE và nó đang hoạt động.
NiksVij 30/09/18

1

Bạn có thể viết hàm bằng của riêng mình:

  • các số bằng nhau nếu: 1) tất cả các khóa bằng nhau, 2) tất cả các giá trị đều bằng nhau
  • danh sách bằng nhau nếu: tất cả các mục bằng nhau và theo cùng một thứ tự
  • nguyên thủy bằng nhau nếu a == b

Bởi vì bạn đang làm việc với json, bạn sẽ có các loại trăn tiêu chuẩn: dict, list, vv, do đó bạn có thể làm kiểm tra kiểu cứng if type(obj) == 'dict':vv

Ví dụ thô sơ (không được thử nghiệm):

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

0

Đối với những người khác muốn gỡ lỗi hai đối tượng JSON (thông thường, có một tham chiếu và một mục tiêu ), đây là một giải pháp bạn có thể sử dụng. Nó sẽ liệt kê " đường dẫn " của những cái khác nhau / không khớp từ đích đến tham chiếu.

level tùy chọn được sử dụng để chọn mức độ sâu bạn muốn xem xét.

show_variables có thể bật tùy chọn để hiển thị biến có liên quan.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.