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.
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
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.
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.
'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)
Vous pouvez écrire votre propre fonction equals:
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