web-dev-qa-db-ja.com

異なる順序で同じ要素を持つ2つのJSONオブジェクトを比較するにはどうすればよいですか?

リストの順序を無視して、Pythonで2つの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は等しく比較する必要があります。

74
user1635536

同じ要素を持ち、順序が異なる2つのオブジェクトを同等に比較したい場合は、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

...しかし、それは動作しません。それぞれの場合、トップレベルの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
105
Zero Piraeus

別の方法は、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

これは、ネストされた辞書とリストに対して機能します。

28
stpk

それらをデコードし、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の回答を参照してください。

15
falsetru

独自の等値関数を作成できます。

  • 1)すべてのキーが等しい、2)すべての値が等しい場合、dictは等しい
  • リストが等しい場合:すべてのアイテムが等しく、同じ順序である
  • 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
1
Gordon Bean

次の2つの辞書 '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

私に間違った結果、つまりfalseを与えました。

そこで、私は次のように独自のカットストーム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

正しい予想出力が得られました!

ロジックは非常に簡単です。

オブジェクトのタイプが「リスト」の場合、最初のリストの各項目を2番目のリストの項目と、見つかるまで比較し、2番目のリストを通過しても項目が見つからない場合、「found」は= falseになります。 「found」値が返されます

それ以外の場合、比較するオブジェクトのタイプが「dict」の場合、両方のオブジェクトのすべてのキーに存在する値を比較します。 (再帰的な比較が実行されます)

それ以外の場合は、単にobj1 == obj2を呼び出します。デフォルトでは、文字列と数字のオブジェクトに対して正常に機能し、それらに対してはeq()が適切に定義されます。

(object2で見つかったアイテムを削除することにより、アルゴリズムをさらに改善できるため、object1の次のアイテムは、object2で既に見つかったアイテムと比較されないことに注意してください)

0
NiksVij