How to compare two JSON objects with the same elements in a different order equal?

How can I check if two JSON objects are equal in python apart from the list order?

For example...

JSON document a :

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

JSON document b :

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

a and b should be compared equal, although the order of the "errors" lists is different.

+61
json python comparison django order
Sep 15 '14 at 15:07
source share
5 answers

If you want two objects with the same elements, but in a different order for comparison, to be equal, then the obvious thing is to compare sorted copies of them - for example, for dictionaries represented by your JSON strings a and 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 

... but this will not work, because in each case the "errors" element of the top-level dictator is a list with the same elements in a different order, and sorted() does not try to sort anything except the "top" level of iteration.

To fix this, we can define the ordered function that will recursively sort any lists it finds (and convert the dictionaries into lists of pairs (key, value) so that they are ordered):

 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 

If we apply this function to a and b , the results are compared equal:

 >>> ordered(a) == ordered(b) True 
+88
Sep 15 '14 at 15:48
source share

Another way could be to use the json.dumps(X, sort_keys=True) option:

 import json a, b = json.dumps(a, sort_keys=True), json.dumps(b, sort_keys=True) a == b # a normal string comparison 

This works for nested dictionaries and lists.

+20
Aug 2 '16 at 13:56 on
source share

Decode them and compare them as a comment in mgilson.

The order does not matter for the dictionary if the keys and values ​​match. (The dictionary has no order in Python)

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

But order is important on the list; sorting will solve the problem for lists.

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

The following example will work for JSON in the question. For a general solution, see Zero Piraeus Answer.

+13
Sep 15 '14 at 15:18
source share

You can write your own equality function:

  • Dictations are equal if: 1) all keys are equal, 2) all values ​​are equal
  • lists are equal if: all elements are equal and in the same order
  • primitives are equal if a == b

Since you are dealing with json, you will have the standard Python types: dict , list , etc., so you can do a hard type check, if type(obj) == 'dict': etc.

A rough example (not verified):

 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
Sep 07 '18 at 17:17
source share

For the next two dictations, 'dictWithListsInValue' and 'reorderedDictWithReorderedListsInValue', which are simply reordered versions of each other

 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 

gave me the wrong result i.e. false.

So I created my own ObjectComparator, like this:

 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 

which gave me the correct expected result!

The logic is pretty simple:

If the objects are of the type “list”, then each element of the first list is compared with the elements of the second list until it is found, and if the element is not found after passing through the second list, then “found” will be false. 'found' value is returned

Otherwise, if the objects being compared are of type 'dict', compare the values ​​present for all the corresponding keys in both objects. (A recursive comparison is performed)

Just call obj1 == obj2. By default, it works fine for an object of strings and numbers, and for them eq () is defined accordingly.

(Note that the algorithm can be further improved by deleting elements found in object2 so that the next element of object1 does not compare with elements already found in object2)

0
Sep 30 '18 at 10:46
source share



All Articles