다른 순서로 동일한 요소를 가진 두 개의 JSON 객체를 동일한 순서로 비교하는 방법은 무엇입니까?


102

목록의 순서에 관계없이 두 개의 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"}
    ]
}

ab의 순서에도 불구하고, 동일한 비교해야 "errors"목록이 다릅니다.



1
왜 그것들을 해독하고 비교하지 않습니까? 아니면 "배열"또는 list요소 의 순서도 중요하지 않다는 뜻입니까?
mgilson 2014-09-15

@ user2085282이 질문에는 다른 문제가 있습니다.
user193661

2
내 순진함을 용서해주세요. 그런데 왜? 목록 요소에는 이유에 대한 특정 순서가 있습니다.
ATOzTOA

1
이 답변에서 언급했듯이 JSON 배열은 정렬되어 정렬 순서가 다른 배열을 포함하는 이러한 객체는 엄격한 의미에서 동일하지 않습니다. stackoverflow.com/a/7214312/18891
Eric Ness

답변:


143

요소가 같지만 순서가 다른 두 개체를 동일하게 비교하려면 다음과 같이 정렬 된 복사본을 비교하는 것이 좋습니다. 예를 들어 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

...하지만 작동하지 않습니다. 왜냐하면 각각의 경우 "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

이 함수를 a및에 적용 b하면 결과가 동일하게 비교됩니다.

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

1
Zero Piraeus에게 정말 감사합니다. 제가 필요로하는 일반적인 해결책입니다. 그러나 유일한 문제는 코드가 python3이 아닌 python 2.x에서만 작동한다는 것입니다. 다음과 같은 오류가 발생합니다. TypeError : unorderable types : dict () <dict () 어쨌든 솔루션은 이제 명확합니다. python3에서 작동하도록 노력할 것입니다. 고마워요

1
@HoussamHsm 처음에 정렬 할 수없는 딕셔너리 문제를 언급했을 때 Python 3.x에서 작동하도록이 문제를 해결하려고했지만 어떻게 든 저에게서 벗어났습니다. 지금 :-) 2.x 및 3.x 모두에서 작동
제로 피레 우스

와 같은 목록 ['astr', {'adict': 'something'}]TypeError있을 때 정렬하려고 할 때 얻었 습니다.
Zhenxiao Hao

1
@ Blairg23 당신은 질문을 오해했습니다.이 질문은 요소가 동일하지만 사전의 가정 된 순서가 아닌 다른 순서로 포함 된 목록을 포함 할 때 JSON 객체를 동일하게 비교 하는 것입니다.
Zero Piraeus

1
@ Blairg23 질문이 더 명확하게 작성 될 수 있다는 데 동의합니다 ( 편집 기록 을 살펴보면 시작된 것보다 낫습니다). 사전 및 주문 : - 재 그래, 내가 아는 ;-)
제로 피레 우스를

45

또 다른 방법은 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

이것은 중첩 된 사전 및 목록에서 작동합니다.


{"error":"a"}, {"error":"b"}{"error":"b"}, {"error":"a"} 첫 번째 경우에 후자의 경우 정렬 할 수 없습니다
크롬 하츠

@ Blairg23하지만 dict에 중첩 된 목록이 있으면 어떻게 하시겠습니까? 당신은 최상위 딕셔너리를 비교하고 그것을 하루라고 부를 수는 없습니다. 이것은이 질문에 관한 것이 아닙니다.
stpk

4
내부에 목록이 있으면 작동하지 않습니다. 예 json.dumps({'foo': [3, 1, 2]}, sort_keys=True) == json.dumps({'foo': [2, 1, 3]}, sort_keys=True)
Danil

7
@Danil과 아마 그렇게해서는 안됩니다. 목록은 순서가 지정된 구조이며 순서 만 다를 경우 다른 것으로 간주해야합니다. 아마도 당신의 유스 케이스에서는 순서가 중요하지 않을 수도 있지만 우리는 그것을 가정해서는 안됩니다.
stpk

목록은 색인별로 정렬되기 때문에 재 지정되지 않습니다. 대부분의 상황에서 [0, 1]은 [1, 0]과 같지 않아야합니다. 따라서 이것은 정상적인 경우에는 좋은 해결책이지만 위의 질문에는 적합하지 않습니다. 여전히 +1
Harrison

18

그것들을 해독하고 mgilson 주석으로 비교하십시오.

키와 값이 일치하는 한 사전에 순서는 중요하지 않습니다. (사전은 파이썬에서 순서가 없습니다)

>>> {'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의 답변을 참조하십시오.


2

다음 두 dicts '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 될 때까지 첫 번째 목록의 각 항목을 두 번째 목록의 항목과 비교하고 두 번째 목록을 통과 한 후에도 항목을 찾을 수 없으면 'found'는 = false가됩니다. '발견 된'값이 반환됩니다.

그렇지 않으면 비교할 개체가 'dict'유형 인 경우 두 개체의 모든 각 키에 대해 존재하는 값을 비교합니다. (재귀 비교가 수행됨)

그렇지 않으면 단순히 obj1 == obj2를 호출하십시오. 기본적으로 문자열과 숫자의 객체에 대해 잘 작동하며 eq ()가 적절하게 정의되어 있습니다.

(object2에서 찾은 항목을 제거하여 알고리즘을 더욱 향상시킬 수 있으므로 object1의 다음 항목은 object2에서 이미 찾은 항목과 비교되지 않습니다.)


코드 들여 쓰기수정할 수 있습니까 ?
colidyre

@colidyre 지금 들여 쓰기 괜찮습니까?
NiksVij

아니요, 여전히 문제가 있습니다. 펑션 헤드 뒤 블록도 들여 쓰기를해야합니다.
colidyre

예. 다시 한 번 수정했습니다. 나는 그것을 IDE에 복사하여 붙여 넣었고 지금 작동하고있다.
NiksVij

1

고유 한 같음 함수를 작성할 수 있습니다.

  • 1) 모든 키가 같고, 2) 모든 값이 같은 경우 dicts는 동일합니다.
  • 목록은 다음과 같은 경우 동일합니다. 모든 항목이 동일하고 순서가 동일합니다.
  • 프리미티브는 다음과 같은 경우 동일합니다. a == b

json을 다루기 때문에 표준 파이썬 유형 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

0

두 개의 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.