Comparez deux objects JSON en Java

Je recherche une bibliothèque d’parsing JSON prenant en charge la comparaison de deux objects JSON en ignorant l’ordre des enfants, en particulier pour les tests unitaires JSON renvoyés par un service Web.

Est-ce que l’une des principales bibliothèques JSON supporte cela? La bibliothèque org.json effectue simplement une comparaison de référence.

En tant que point d’architecture générale, je déconseille généralement de laisser des dépendances sur un format de sérialisation particulier déborder de votre couche de stockage / réseau; Par conséquent, je vous recommande de tester l’égalité entre vos propres objects applicatifs plutôt que leurs manifestations JSON.

Cela étant dit, je suis actuellement un grand fan de Jackson que ma lecture rapide de leur implémentation ObjectNode.equals () suggère la comparaison d’appartenance souhaitée:

 public boolean equals(Object o) { if (o == this) return true; if (o == null) return false; if (o.getClass() != getClass()) { return false; } ObjectNode other = (ObjectNode) o; if (other.size() != size()) { return false; } if (_children != null) { for (Map.Entry en : _children.entrySet()) { Ssortingng key = en.getKey(); JsonNode value = en.getValue(); JsonNode otherValue = other.get(key); if (otherValue == null || !otherValue.equals(value)) { return false; } } } return true; } 

Essayez JSONAssert de Skyscreamer .

Son mode non ssortingct présente deux avantages majeurs qui le rendent moins fragile:

  • Extensibilité d’object (par exemple, avec une valeur attendue de {id: 1} , cela passerait toujours: {id: 1, moredata: ‘x’} .)
  • Ordre en vrac (p. Ex. [‘Chien’, ‘chat’] == [‘chat’, ‘chien’])

En mode ssortingct, il se comporte plus comme la classe de test de json-lib.

Un test ressemble à ceci:

 @Test public void testGetFriends() { JSONObject data = getRESTData("/friends/367.json"); Ssortingng expected = "{friends:[{id:123,name:\"Corby Page\"}" + ",{id:456,name:\"Solomon Duskis\"}]}"; JSONAssert.assertEquals(expected, data, false); } 

Les parameters de l’appel JSONAssert.assertEquals () sont expectedJSONSsortingng , actualDataSsortingng et isSsortingct .

Les messages de résultat sont assez clairs, ce qui est important lors de la comparaison de très gros objects JSON.

Utiliser GSON

 JsonParser parser = new JsonParser(); JsonElement o1 = parser.parse("{a : {a : 2}, b : 2}"); JsonElement o2 = parser.parse("{b : 2, a : {a : 2}}"); assertEquals(o1, o2); 

Je ferais ce qui suit,

 final JSONObject obj1 = /*json*/; final JSONObject obj2 = /*json*/; final ObjectMapper mapper = new ObjectMapper(); final JsonNode tree1 = mapper.readTree(obj1.toSsortingng()); final JsonNode tree2 = mapper.readTree(obj2.toSsortingng()); return tree1.equals(tree2); 

Vous pouvez essayer d’utiliser la classe JSONAssert de json-lib:

 JSONAssert.assertEquals( "{foo: 'bar', baz: 'qux'}", JSONObject.fromObject("{foo: 'bar', baz: 'xyzzy'}")); 

Donne:

 junit.framework.ComparisonFailure: objects differed at key [baz]; expected:<[qux]> but was:<[xyzzy]> 

Si vous utilisez déjà JUnit, la dernière version utilise désormais Hamcrest. C’est un cadre de correspondance générique (particulièrement utile pour les tests unitaires) qui peut être étendu pour créer de nouveaux matchers.

Il existe une petite bibliothèque open source appelée hamcrest-json avec des correspondances compatibles JSON. Il est bien documenté, testé et pris en charge. Voici quelques liens utiles:

  • Code source
  • Page d’accueil
  • Javadocs pour le matcher principal :

Exemple de code utilisant des objects de la bibliothèque JSON org.json.simple :

 Assert.assertThat( jsonObject1.toJSONSsortingng(), SameJSONAs.sameJSONAs(jsonObject2.toJSONSsortingng())); 

Vous pouvez éventuellement (1) autoriser les tableaux “tout-ordre” et (2) ignorer les champs supplémentaires.

Comme il existe une variété de bibliothèques JSON pour Java ( Jackson , GSON , json-lib , etc.), il est utile que hamcrest-json charge le texte JSON (comme java.lang.Ssortingng ), ainsi que les objects prenant en charge nativement Douglas. La bibliothèque JSON de Crockford org.json .

Enfin, si vous n’utilisez pas JUnit, vous pouvez utiliser Hamcrest directement pour les assertions. ( J’ai écrit à ce sujet ici. )

Utilisez cette bibliothèque: https://github.com/lukas-krecan/JsonUnit

Pom:

  net.javacrumbs.json-unit json-unit 1.5.0 test  

IGNORING_ARRAY_ORDER – ignore l’ordre dans les tableaux

 assertJsonEquals("{\"test\":[1,2,3]}", "{\"test\": [3,2,1]}",when(IGNORING_ARRAY_ORDER)); 

Vous pouvez essayer JsonUnit . Il peut comparer deux objects JSON et signaler les différences. Il est construit sur Jackson.

Par exemple

 assertJsonEquals("{\"test\":1}", "{\n\"test\": 2\n}"); 

Résulte en

 java.lang.AssertionError: JSON documents are different: Different value found in node "test". Expected 1, got 2. 

Une chose que j’ai faite et ça marche à merveille est de lire les deux objects dans HashMap et de les comparer avec un assertEquals () normal. Elle appellera la méthode equals () des hashmaps, qui comparera récursivement tous les objects à l’intérieur (ils seront soit d’autres hashmaps, soit un object à valeur unique comme une chaîne ou un entier). Cela a été fait en utilisant l’parsingur Jackson JSON de Codehaus.

 assertEquals(mapper.readValue(expectedJson, new TypeReference>(){}), mapper.readValue(actualJson, new TypeReference>(){})); 

Une approche similaire peut être utilisée si l’object JSON est un tableau à la place.

Pour org.json, j’ai déployé ma propre solution, une méthode comparable aux instances JSONObject. Je n’ai pas travaillé avec des objects JSON complexes dans ce projet, donc je ne sais pas si cela fonctionne dans tous les scénarios. De plus, étant donné que je l’utilise dans les tests unitaires, je n’ai pas fait d’efforts pour optimiser les résultats. C’est ici:

 public static boolean jsonObjsAreEqual (JSONObject js1, JSONObject js2) throws JSONException { if (js1 == null || js2 == null) { return (js1 == js2); } List l1 = Arrays.asList(JSONObject.getNames(js1)); Collections.sort(l1); List l2 = Arrays.asList(JSONObject.getNames(js2)); Collections.sort(l2); if (!l1.equals(l2)) { return false; } for (Ssortingng key : l1) { Object val1 = js1.get(key); Object val2 = js2.get(key); if (val1 instanceof JSONObject) { if (!(val2 instanceof JSONObject)) { return false; } if (!jsonObjsAreEqual((JSONObject)val1, (JSONObject)val2)) { return false; } } if (val1 == null) { if (val2 != null) { return false; } } else if (!val1.equals(val2)) { return false; } } return true; } 

J’utilise ceci et ça marche bien pour moi (avec org.json. *):

 package com.project1.helpers; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; public class JSONUtils { public static boolean areEqual(Object ob1, Object ob2) throws JSONException { Object obj1Converted = convertJsonElement(ob1); Object obj2Converted = convertJsonElement(ob2); return obj1Converted.equals(obj2Converted); } private static Object convertJsonElement(Object elem) throws JSONException { if (elem instanceof JSONObject) { JSONObject obj = (JSONObject) elem; Iterator keys = obj.keys(); Map jsonMap = new HashMap<>(); while (keys.hasNext()) { String key = keys.next(); jsonMap.put(key, convertJsonElement(obj.get(key))); } return jsonMap; } else if (elem instanceof JSONArray) { JSONArray arr = (JSONArray) elem; Set jsonSet = new HashSet<>(); for (int i = 0; i < arr.length(); i++) { jsonSet.add(convertJsonElement(arr.get(i))); } return jsonSet; } else { return elem; } } } 

Je prendrais la bibliothèque à http://json.org/java/ et modifierais la méthode equals de JSONObject et JSONArray pour effectuer un test d’égalité approfondie. Pour vous assurer que cela fonctionne indépendamment de l’ordre des enfants, il suffit de remplacer la map interne par un TreeMap ou d’utiliser quelque chose comme Collections.sort() .

Essaye ça:

 public static boolean jsonsEqual(Object obj1, Object obj2) throws JSONException { if (!obj1.getClass().equals(obj2.getClass())) { return false; } if (obj1 instanceof JSONObject) { JSONObject jsonObj1 = (JSONObject) obj1; JSONObject jsonObj2 = (JSONObject) obj2; Ssortingng[] names = JSONObject.getNames(jsonObj1); Ssortingng[] names2 = JSONObject.getNames(jsonObj1); if (names.length != names2.length) { return false; } for (Ssortingng fieldName:names) { Object obj1FieldValue = jsonObj1.get(fieldName); Object obj2FieldValue = jsonObj2.get(fieldName); if (!jsonsEqual(obj1FieldValue, obj2FieldValue)) { return false; } } } else if (obj1 instanceof JSONArray) { JSONArray obj1Array = (JSONArray) obj1; JSONArray obj2Array = (JSONArray) obj2; if (obj1Array.length() != obj2Array.length()) { return false; } for (int i = 0; i < obj1Array.length(); i++) { boolean matchFound = false; for (int j = 0; j < obj2Array.length(); j++) { if (jsonsEqual(obj1Array.get(i), obj2Array.get(j))) { matchFound = true; break; } } if (!matchFound) { return false; } } } else { if (!obj1.equals(obj2)) { return false; } } return true; } 

Vous pouvez utiliser la bibliothèque zjsonpatch , qui présente les informations de diff conformément à la RFC 6902 (JSON Patch). C’est très facile à utiliser. S’il vous plaît visitez sa page de description pour son utilisation

Je sais que cela est généralement considéré uniquement pour les tests, mais vous pouvez utiliser le JSC comparitorSameJSONAs de Hamcrest dans JSON Hamcrest.

Hamcrest JSON SameJSONA

Pour ceux qui comme moi veulent faire cela avec Jackson, vous pouvez utiliser json-unit .

 JsonAssert.assertJsonEquals(jsonNode1, jsonNode2); 

Les erreurs donnent des informations utiles sur le type de non-concordance:

 java.lang.AssertionError: JSON documents have different values: Different value found in node "heading.content[0].tag[0]". Expected 10209, got 10206. 

Le karaté est exactement ce que vous recherchez. Voici un exemple:

 * def myJson = { foo: 'world', hey: 'ho', zee: [5], cat: { name: 'Billie' } } * match myJson = { cat: { name: 'Billie' }, hey: 'ho', foo: 'world', zee: [5] } 

(avertissement: dev ici)

Rien d’autre ne semblait fonctionner correctement, alors j’ai écrit ceci:

 private boolean jsonEquals(JsonNode actualJson, JsonNode expectJson) { if(actualJson.getNodeType() != expectJson.getNodeType()) return false; switch(expectJson.getNodeType()) { case NUMBER: return actualJson.asDouble() == expectJson.asDouble(); case STRING: case BOOLEAN: return actualJson.asText().equals(expectJson.asText()); case OBJECT: if(actualJson.size() != expectJson.size()) return false; Iterator fieldIterator = actualJson.fieldNames(); while(fieldIterator.hasNext()) { Ssortingng fieldName = fieldIterator.next(); if(!jsonEquals(actualJson.get(fieldName), expectJson.get(fieldName))) { return false; } } break; case ARRAY: if(actualJson.size() != expectJson.size()) return false; List remaining = new ArrayList<>(); expectJson.forEach(remaining::add); // O(N^2) for(int i=0; i < actualJson.size(); ++i) { boolean oneEquals = false; for(int j=0; j < remaining.size(); ++j) { if(jsonEquals(actualJson.get(i), remaining.get(j))) { oneEquals = true; remaining.remove(j); break; } } if(!oneEquals) return false; } break; default: throw new IllegalStateException(); } return true; } 

Le code suivant sera plus utile pour comparer deux JsonObject, JsonArray, JsonPrimitive et JasonElements.

 private boolean compareJson(JsonElement json1, JsonElement json2) { boolean isEqual = true; // Check whether both jsonElement are not null if (json1 != null && json2 != null) { // Check whether both jsonElement are objects if (json1.isJsonObject() && json2.isJsonObject()) { Set> ens1 = ((JsonObject) json1).entrySet(); Set> ens2 = ((JsonObject) json2).entrySet(); JsonObject json2obj = (JsonObject) json2; if (ens1 != null && ens2 != null) { // (ens2.size() == ens1.size()) // Iterate JSON Elements with Key values for (Entry en : ens1) { isEqual = isEqual && compareJson(en.getValue(), json2obj.get(en.getKey())); } } else { return false; } } // Check whether both jsonElement are arrays else if (json1.isJsonArray() && json2.isJsonArray()) { JsonArray jarr1 = json1.getAsJsonArray(); JsonArray jarr2 = json2.getAsJsonArray(); if (jarr1.size() != jarr2.size()) { return false; } else { int i = 0; // Iterate JSON Array to JSON Elements for (JsonElement je : jarr1) { isEqual = isEqual && compareJson(je, jarr2.get(i)); i++; } } } // Check whether both jsonElement are null else if (json1.isJsonNull() && json2.isJsonNull()) { return true; } // Check whether both jsonElement are primitives else if (json1.isJsonPrimitive() && json2.isJsonPrimitive()) { if (json1.equals(json2)) { return true; } else { return false; } } else { return false; } } else if (json1 == null && json2 == null) { return true; } else { return false; } return isEqual; } 

Pour comparer jsons, je recommande d’utiliser JSONCompare: https://github.com/fslev/json-compare

 // Compare by regex Ssortingng expected = "{\"a\":\".*me.*\"}"; Ssortingng actual = "{\"a\":\"some text\"}"; JSONCompare.assertEquals(expected, actual); // True // Check expected array has no extra elements Ssortingng expected = "[1,\"test\",4,\"!.*\"]"; Ssortingng actual = "[4,1,\"test\"]"; JSONCompare.assertEquals(expected, actual); // True // Check expected array has no numbers Ssortingng expected = "[\"\\\\\\d+\"]"; Ssortingng actual = "[\"text\",\"test\"]"; JSONCompare.assertEquals(expected, actual); // True // Check expected array has no numbers Ssortingng expected = "[\"\\\\\\d+\"]"; Ssortingng actual = "[2018]"; JSONCompare.assertNotEquals(expected, actual); // True 

jsonObject implémente une interface comparable, essayez d’utiliser collection.sort ().

Cette solution pour moi, le travail est très bien:

 try { // Getting The Array "Courses" from json1 & json2 Courses1 =json1.getJSONArray(TAG_COURSES1); Courses2 = json2.getJSONArray(TAG_COURSES); //LOOP FOR JSON1 for(int i = 0; i < Courses1.length(); i++){ //LOOP FOR JSON2 for(int ii = 0; ii < Courses2.length(); ii++){ JSONObject courses1 = Courses1.getJSONObject(i); JSONObject courses2 = Courses2.getJSONObject(ii); // Storing each json1 item in variable int courseID1 = courses1.getInt(TAG_COURSEID1); Log.e("COURSEID2:", Integer.toString(courseID1)); String Rating1 = courses1.getString(TAG_RATING1); int Status1 = courses1.getInt(TAG_STATUS1); Log.e("Status1:", Integer.toString(Status1)); //Put the actual value for Status1 in log. // Storing each json2 item in variable int courseID2 = courses2.getInt(TAG_COURSEID); Log.e("COURSEID2:", Integer.toString(courseID)); //Put the actual value for CourseID in log String Title2 = courses2.getString(TAG_TITLE); String instructor2 = courses2.getString(TAG_INSTRUCTOR); String length2 = courses2.getString(TAG_LENGTH); String rating2 = courses2.getString(TAG_RATING); String subject2 = courses2.getString(TAG_SUBJECT); String description2 = courses2.getString(TAG_DESCRIPTION); //Status1 = 5 from json1; Incomplete, Status1 =-1 Complete if(Status1 == 5 && courseID2 == courseID1){ // creating new HashMap HashMap map = new HashMap(); //Storing the elements if condition is true. map.put(TAG_COURSEID, Integer.toSsortingng(courseID2)); //pend for compare map.put(TAG_TITLE, Title2); map.put(TAG_INSTRUCTOR, instructor2); map.put(TAG_LENGTH, length2); map.put(TAG_RATING, rating2); map.put(TAG_SUBJECT, subject2); //show it map.put(TAG_DESCRIPTION, description2); //adding HashList to ArrayList contactList.add(map); }//if }//for2 (json2) } //for1 (json1) }//Try 

J’espère que cela aidera les autres.