Wie kann ich testen, ob zwei JSON-Objekte in Python gleich sind, wobei die Reihenfolge der Listen ignoriert wird?
Zum Beispiel ...
JSON-Dokument a :
{
"errors": [
{"error": "invalid", "field": "email"},
{"error": "required", "field": "name"}
],
"success": false
}
JSON-Dokument b :
{
"success": false,
"errors": [
{"error": "required", "field": "name"},
{"error": "invalid", "field": "email"}
]
}
a
und b
sollten gleich sein, auch wenn die Reihenfolge der "errors"
-Listen unterschiedlich ist.
Wenn Sie möchten, dass zwei Objekte mit den gleichen Elementen, aber in einer anderen Reihenfolge gleich verglichen werden, vergleichen Sie sortierte Kopien davon - zum Beispiel für die Wörterbücher, die durch Ihre JSON-Zeichenfolgen a
und b
dargestellt werden:
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
... aber das funktioniert nicht, denn in jedem Fall ist das "errors"
-Element des obersten Diktats eine Liste mit den gleichen Elementen in einer anderen Reihenfolge. sorted()
versucht nichts außer "top" zu sortieren. Stufe eines Iterablen.
Um dies zu beheben, können wir eine ordered
-Funktion definieren, die alle gefundenen Listen rekursiv sortiert (und Wörterbücher in Listen von (key, value)
-Paaren konvertiert, um sie zu ordnen):
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
Wenn wir diese Funktion auf a
und b
anwenden, sind die Ergebnisse gleich:
>>> ordered(a) == ordered(b)
True
Eine andere Möglichkeit könnte die json.dumps(X, sort_keys=True)
-Option sein:
import json
a, b = json.dumps(a, sort_keys=True), json.dumps(b, sort_keys=True)
a == b # a normal string comparison
Dies funktioniert für verschachtelte Wörterbücher und Listen.
Dekodiere sie und vergleiche sie als mgilson-Kommentar.
Die Reihenfolge spielt für das Wörterbuch keine Rolle, solange die Schlüssel und Werte übereinstimmen. (Wörterbuch hat keine Reihenfolge in Python)
>>> {'a': 1, 'b': 2} == {'b': 2, 'a': 1}
True
Aber die Reihenfolge ist wichtig in der Liste. Sortierung löst das Problem für die Listen.
>>> [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
Das obige Beispiel funktioniert für die JSON in der Frage. Eine allgemeine Lösung finden Sie in der Antwort von Zero Piraeus.
Für die folgenden beiden Wörter 'dictWithListsInValue' und 'reorderedDictWithReorderedListsInValue' sind dies einfach umgeordnete Versionen
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
gab mir ein falsches Ergebnis, d. h. falsch.
Also habe ich meinen eigenen cutstom ObjectComparator so erstellt:
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
was gab mir die richtige erwartete Ausgabe!
Logik ist ziemlich einfach:
Wenn die Objekte vom Typ "Liste" sind, vergleichen Sie jedes Element der ersten Liste mit den Elementen der zweiten Liste, bis es gefunden wird. Wenn das Element nach dem Durchlaufen der zweiten Liste nicht gefunden wird, wäre "found" = false. Der gefundene Wert wird zurückgegeben
Andernfalls, wenn die zu vergleichenden Objekte vom Typ 'dict' sind, vergleichen Sie die Werte, die für alle entsprechenden Schlüssel in beiden Objekten vorhanden sind. (Rekursiver Vergleich wird durchgeführt)
Ansonsten rufen Sie einfach obj1 == obj2 auf. Es funktioniert standardmäßig gut für das Objekt von Strings und Zahlen und für die eq () wird es entsprechend definiert.
(Beachten Sie, dass der Algorithmus durch das Entfernen der in object2 gefundenen Elemente weiter verbessert werden kann, sodass sich das nächste Objekt von object1 nicht mit den bereits in object2 gefundenen Elementen vergleicht.)
Sie können Ihre eigene Equals-Funktion schreiben:
a == b
Da Sie sich mit Json beschäftigen, haben Sie Standard-Python-Typen: dict
, list
usw., so dass Sie hartes Prüfen von if type(obj) == 'dict':
usw.
Grobes Beispiel (nicht getestet):
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