リストの順序を無視して、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"
リストの順序が異なっていても、a
とb
は等しく比較する必要があります。
同じ要素を持ち、順序が異なる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
この関数をa
とb
に適用すると、結果は等しくなります:
>>> ordered(a) == ordered(b)
True
別の方法は、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
これは、ネストされた辞書とリストに対して機能します。
それらをデコードし、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の回答を参照してください。
独自の等値関数を作成できます。
a == b
の場合、プリミティブは等しいJsonを扱っているため、標準のpython型があります:dict
、list
など。したがって、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
次の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で既に見つかったアイテムと比較されないことに注意してください)