¿Cómo comparar dos objetos JSON con los mismos elementos en un orden diferente igual?

¿Cómo puedo probar si dos objetos JSON son iguales en python, sin tener en cuenta el orden de las listas?

Por ejemplo …

Documento JSON a :

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

Documento JSON b :

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

a y b deben ser iguales, aunque el orden de las listas de "errors" sea ​​diferente.

Si desea dos objetos con los mismos elementos pero en un orden diferente para comparar iguales, entonces lo obvio es comparar copias ordenadas de ellos, por ejemplo, para los diccionarios representados por las cadenas JSON a y 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 

… pero eso no funciona, porque en cada caso, el elemento de "errors" del dict de nivel superior es una lista con los mismos elementos en un orden diferente, y sorted() no intenta ordenar nada excepto El nivel “superior” de un iterable.

Para solucionarlo, podemos definir una función ordered que ordenará recursivamente las listas que encuentre (y convertiremos los diccionarios en listas de pares (key, value) para que sean ordenados):

 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 aplicamos esta función a a y b , los resultados se comparan igual:

 >>> ordered(a) == ordered(b) True 

Otra forma podría ser usar la 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 

Esto funciona para diccionarios y listas anidadas.

Descodificarlos y compararlos como comentario de mgilson.

El orden no importa para el diccionario siempre que las claves y los valores coincidan. (El diccionario no tiene orden en Python)

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

Pero el orden es importante en la lista; La clasificación resolverá el problema de las listas.

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

El ejemplo anterior funcionará para el JSON en la pregunta. Para la solución general, vea la respuesta de Zero Piraeus.

Puedes escribir tu propia función de iguales:

  • los dados son iguales si: 1) todas las claves son iguales, 2) todos los valores son iguales
  • las listas son iguales si: todos los elementos son iguales y en el mismo orden
  • primitivas son iguales si a == b

Debido a que está tratando con json, tendrá tipos de python estándar: dict , list , etc., por lo que puede hacer una comprobación de tipo difícil if type(obj) == 'dict': etc.

Ejemplo aproximado (no probado):

 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 

Para los siguientes dos dictados ‘dictWithListsInValue’ y ‘reorderedDictWithReorderedListsInValue’ que son simplemente versiones reordenadas entre sí

 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 

Me dio resultado equivocado, es decir, falso.

Así que creé mi propio ObjectComparator de cutstom así:

 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 

Lo que me dio la salida correcta esperada!

La lógica es bastante simple:

Si los objetos son del tipo ‘lista’, compare cada elemento de la primera lista con los elementos de la segunda lista hasta que se encuentre, y si el elemento no se encuentra después de pasar por la segunda lista, entonces ‘encontrado’ sería = falso. se devuelve el valor ‘encontrado’

De lo contrario, si los objetos que se van a comparar son del tipo ‘dict’, compare los valores presentes para todas las claves respectivas en ambos objetos. (Se realiza comparación recursiva)

Si no, simplemente llame a obj1 == obj2. Por defecto, funciona bien para el objeto de cadenas y números y para esos eq () se define adecuadamente.

(Tenga en cuenta que el algoritmo puede mejorarse aún más eliminando los elementos que se encuentran en object2, de modo que el siguiente elemento de object1 no se compare con los elementos que ya se encuentran en el objeto2)