web-dev-qa-db-fra.com

Comment comparer deux objets JSON avec les mêmes éléments dans un ordre différent égal?

Comment puis-je tester si deux objets JSON sont égaux en python, indépendamment de l'ordre des listes?

Par exemple ...

Document JSON a :

{
    "errors": [
        {"error": "invalid", "field": "email"},
        {"error": "required", "field": "name"}
    ],
    "success": false
}

Document JSON b :

{
    "success": false,
    "errors": [
        {"error": "required", "field": "name"},
        {"error": "invalid", "field": "email"}
    ]
}

a et b doivent se comparer, bien que l'ordre des listes "errors" soit différent.

61
Houssam Hsm

Si vous voulez que deux objets avec les mêmes éléments mais dans un ordre différent soient comparés égaux, il est évident de comparer des copies triées de ces objets, par exemple pour les dictionnaires représentés par vos chaînes JSON a et 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

... mais cela ne fonctionne pas, car dans chaque cas, l'item "errors" du dict de niveau supérieur est une liste avec les mêmes éléments dans un ordre différent, et sorted() n'essaie pas de trier autre chose que le "haut" niveau d'un iterable.

Pour résoudre ce problème, nous pouvons définir une fonction ordered qui triera de manière récursive toutes les listes trouvées (et convertira les dictionnaires en listes de paires (key, value) afin qu'elles soient ordonnables):

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

Si nous appliquons cette fonction à a et b, les résultats se comparent égaux:

>>> ordered(a) == ordered(b)
True
90
Zero Piraeus

Une autre façon pourrait être d'utiliser l'option 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

Cela fonctionne pour les dictionnaires et les listes imbriquées.

20
stpk

Décodez-les et comparez-les comme commentaire mgilson.

L'ordre n'a pas d'importance pour le dictionnaire tant que les clés et les valeurs correspondent. (Le dictionnaire n'a pas d'ordre en Python)

>>> {'a': 1, 'b': 2} == {'b': 2, 'a': 1}
True

Mais l'ordre est important dans la liste; le tri résoudra le problème pour les listes.

>>> [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

L'exemple ci-dessus fonctionnera pour le JSON dans la question. Pour une solution générale, voir la réponse de Zero Piraeus.

13
falsetru

'DictWithListsInValue' et 'reorderedDictWithReorderedListsInValue' des deux dictées suivantes, qui sont simplement des versions réorganisées de l'autre

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

m'a donné un résultat erroné, c'est-à-dire faux.

J'ai donc créé mon propre ObjectComparator coupé comme ceci:

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

ce qui m'a donné la sortie attendue correcte!

La logique est assez simple:

Si les objets sont du type 'liste', comparez chaque élément de la première liste avec ceux de la deuxième liste jusqu'à ce qu'ils soient trouvés, et si l'élément n'est pas trouvé après avoir parcouru la deuxième liste, alors 'trouvé' serait = faux. la valeur 'trouvée' est renvoyée

Sinon, si les objets à comparer sont de type 'dict', comparez les valeurs présentes pour toutes les clés respectives dans les deux objets. (La comparaison récursive est effectuée)

Sinon, appelez simplement obj1 == obj2. Cela fonctionne par défaut très bien pour l’objet de chaînes et de nombres et pour ceux eq () est défini de manière appropriée.

(Notez que l'algorithme peut encore être amélioré en supprimant les éléments trouvés dans object2, afin que l'élément suivant d'objet1 ne puisse pas se comparer avec les éléments déjà trouvés dans l'objet2)

0
NiksVij

Vous pouvez écrire votre propre fonction equals:

  • les chiffres sont égaux si: 1) toutes les clés sont égales, 2) toutes les valeurs sont égales
  • les listes sont égales si: tous les éléments sont égaux et dans le même ordre
  • les primitives sont égales si a == b

Comme vous avez affaire à json, vous aurez des types python standard: dict, list, etc., de sorte que vous pouvez effectuer une vérification de type difficile if type(obj) == 'dict':, etc.

Exemple approximatif (non testé):

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