it-swarm.com.de

Wie kann man zwei JSON-Objekte mit den gleichen Elementen in einer anderen Reihenfolge gleich vergleichen?

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.

61
Houssam Hsm

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
90
Zero Piraeus

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.

20
stpk

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.

13
falsetru

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.)

0
NiksVij

Sie können Ihre eigene Equals-Funktion schreiben:

  • diagramme sind gleich, wenn: 1) alle Schlüssel gleich sind, 2) alle Werte gleich sind
  • listen sind gleich, wenn: alle Elemente gleich und in derselben Reihenfolge sind
  • grundelemente sind gleich, wenn 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
0
Gordon Bean