Compara dos objetos JSON en Java

Estoy buscando una biblioteca de análisis JSON que sea compatible con la comparación de dos objetos JSON que ignoran la orden de los niños, específicamente para la prueba unitaria de JSON que regresa de un servicio web.

¿Alguna de las principales bibliotecas JSON admite esto? La biblioteca org.json simplemente hace una comparación de referencia.

Como punto arquitectónico general, generalmente desaconsejo dejar que las dependencias de un formato de serialización particular se desborden más allá de su capa de almacenamiento / red; por lo tanto, primero recomiendo que considere probar la igualdad entre sus propios objetos de aplicación en lugar de sus manifestaciones JSON.

Habiendo dicho eso, actualmente soy un gran admirador de Jackson, y mi rápida lectura de la implementación de ObjectNode.equals () sugiere la comparación de membresía establecida que desea:

 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()) { String key = en.getKey(); JsonNode value = en.getValue(); JsonNode otherValue = other.get(key); if (otherValue == null || !otherValue.equals(value)) { return false; } } } return true; } 

Prueba el JSONAssert de Skyscreamer .

Su modo no estricto tiene dos ventajas principales que lo hacen menos frágil:

  • La extensibilidad del objeto (por ejemplo, con un valor esperado de {id: 1} , esto aún pasaría: {id: 1, moredata: ‘x’} .)
  • Ordenamiento de arreglo suelto (ej. [‘Dog’, ‘cat’] == [‘cat’, ‘dog’])

En modo estricto se comporta más como la clase de prueba de json-lib.

Una prueba se ve así:

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

Los parámetros en la llamada JSONAssert.assertEquals () se esperanJSONString , actualDataString e isStrict .

Los mensajes resultantes son bastante claros, lo cual es importante cuando se comparan objetos JSON realmente grandes.

Usando 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); 

Yo haría lo siguiente,

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

Podría intentar usar la clase JSONAssert de json-lib:

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

Da:

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

Si ya está utilizando JUnit, la última versión ahora emplea Hamcrest. Es un marco de coincidencia genérico (especialmente útil para las pruebas unitarias) que se puede ampliar para crear nuevos equilibradores.

Hay una pequeña biblioteca de código abierto llamada hamcrest-json con coincidencias compatibles con JSON. Está bien documentado, probado y respaldado. A continuación se encuentran algunos enlaces útiles:

  • Código fuente
  • Página de inicio
  • Javadocs para el matcher principal :

Código de ejemplo que usa objetos de la biblioteca JSON org.json.simple :

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

Opcionalmente, puede (1) permitir matrices de “cualquier orden” y (2) ignorar campos adicionales.

Dado que hay una gran variedad de bibliotecas JSON para Java ( Jackson , GSON , json-lib , etc.), es útil que hamcrest-json compatible con texto JSON (como java.lang.String ), así como con el soporte nativo de objetos de Douglas. Crockford’s JSON library org.json .

Finalmente, si no está utilizando JUnit, puede usar Hamcrest directamente para las afirmaciones. ( Escribí sobre esto aquí )

Utilice esta biblioteca: https://github.com/lukas-krecan/JsonUnit

Pom:

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

IGNORING_ARRAY_ORDER – ignora el orden en las matrices

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

Puedes probar JsonUnit . Puede comparar dos objetos JSON e informar diferencias. Está construido encima de Jackson.

Por ejemplo

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

Resultados en

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

Una cosa que hice y que funciona de maravillas es leer ambos objetos en HashMap y luego compararlos con un assertEquals () regular. Llamará al método equals () de los hashmaps, que comparará recursivamente todos los objetos que están dentro (serán otros hashmaps o algún objeto de valor único como una cadena o un entero). Esto se hizo usando el analizador Jackson JSON de Codehaus.

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

Se puede usar un enfoque similar si el objeto JSON es una matriz en su lugar.

Para org.json, he desarrollado mi propia solución, un método que se compara con las instancias de JSONObject. No trabajé con objetos JSON complejos en ese proyecto, así que no sé si esto funciona en todos los escenarios. Además, dado que uso esto en pruebas unitarias, no puse esfuerzo en las optimizaciones. Aquí está:

 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 (String 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; } 

Estoy usando esto, y funciona bien para mí (con 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; } } } 

Tomaría la biblioteca en http://json.org/java/ y modificaré el método equals de JSONObject y JSONArray para hacer una prueba profunda de igualdad. Para asegurarse de que funciona sin graduación del orden de los niños, todo lo que necesita hacer es reemplazar el mapa interno con un TreeMap o usar algo como Collections.sort() .

Prueba esto:

 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; String[] names = JSONObject.getNames(jsonObj1); String[] names2 = JSONObject.getNames(jsonObj1); if (names.length != names2.length) { return false; } for (String 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; } 

Puede usar la biblioteca zjsonpatch , que presenta la información de diferencia de acuerdo con RFC 6902 (revisión JSON). Es muy fácil de usar. Por favor visite su página de descripción para su uso

Sé que generalmente se considera solo para pruebas, pero puede usar el comparador Hamson JSON de Hamcrest JSON.

Hamcrest JSON SameJSONAs

Para aquellos que quieran hacer esto con Jackson, pueden usar json-unit .

 JsonAssert.assertJsonEquals(jsonNode1, jsonNode2); 

Los errores proporcionan información útil sobre el tipo de desajuste:

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

Karate es exactamente lo que estás buscando. Aquí hay un ejemplo:

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

(descargo de responsabilidad: dev aquí)

Nada más parecía funcionar del todo bien, así que escribí esto:

 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()) { String 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; } 

El siguiente código será más útil para comparar dos JsonObject, JsonArray, JsonPrimitive y 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; } 

Para comparar jsons, recomiendo usar JSONCompare: https://github.com/fslev/json-compare

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

jsonObject implementa una interfaz comparable, intenta usar collection.sort ().

Esta solución para mí, el trabajo es muy bueno:

 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.toString(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 

Espero que esto ayude a otros.