Je recherche une bibliothèque d'analyse JSON qui prend en charge la comparaison de deux objets JSON en ignorant l'ordre des enfants, en particulier pour les tests unitaires JSON revenant d'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érences.
En règle générale, je déconseille de laisser les dépendances sur un format de sérialisation particulier disparaître au-delà de votre couche de stockage/réseau; Par conséquent, je vous recommanderais d’abord d’envisager de tester l’égalité entre vos propres objets d’application plutôt que leurs manifestations JSON.
Cela dit, je suis actuellement un grand fan de Jackson que ma rapide lecture de leur ObjectNode.equals () implementation suggère que la comparaison des membres définie que vous souhaitez:
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<String, JsonNode> 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;
}
Essayez Skyscreamer JSONAssert .
Son mode non strict présente deux avantages majeurs qui le rendent moins fragile:
En mode strict, il se comporte davantage comme la classe de test de json-lib.
Un test ressemble à ceci:
@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);
}
Les paramètres de l'appel JSONAssert.assertEquals () sont attendus à JSONString, actualDataString et isStrict.
Les messages de résultat sont assez clairs, ce qui est important lorsque l'on compare de très gros objets 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.toString());
final JsonNode tree2 = mapper.readTree(obj2.toString());
return tree1.equals(tree2);
Vous pouvez essayer d'utiliser JSONAssert class 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]>
Vous pouvez essayer JsonUnit . Il peut comparer deux objets JSON et signaler des 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.
Si vous utilisez déjà JUnit, la dernière version utilise désormais Hamcrest. C'est un framework de correspondance générique (particulièrement utile pour les tests unitaires) qui peut être étendu pour créer de nouveaux correspondants.
Il existe une petite bibliothèque open source appelée hamcrest-json
avec des correspondances prenant en charge JSON. Il est bien documenté, testé et supporté. Voici quelques liens utiles:
Exemple de code utilisant des objets de la bibliothèque JSON org.json.simple
:
Assert.assertThat(
jsonObject1.toJSONString(),
SameJSONAs.sameJSONAs(jsonObject2.toJSONString()));
Vous pouvez éventuellement (1) autoriser les tableaux "à tout ordre" et (2) ignorer les champs supplémentaires.
Comme il existe diverses bibliothèques JSON pour Java (Jackson
, GSON
, json-lib
, etc.), il est utile que hamcrest-json
prenne en charge le texte JSON (sous la forme Java.lang.String
), ainsi que les objets pris en charge de manière native dans la bibliothèque JSON de Douglas Crockford org.json
.
Enfin, si vous n'utilisez pas JUnit, vous pouvez utiliser directement Hamcrest pour les assertions. ( J'ai écrit à propos de ça ici. )
Utilisez cette bibliothèque: https://github.com/lukas-krecan/JsonUnit
Pom:
<dependency>
<groupId>net.javacrumbs.json-unit</groupId>
<artifactId>json-unit</artifactId>
<version>1.5.0</version>
<scope>test</scope>
</dependency>
IGNORING_ARRAY_ORDER - ignore l'ordre dans les tableaux
assertJsonEquals("{\"test\":[1,2,3]}",
"{\"test\": [3,2,1]}",when(IGNORING_ARRAY_ORDER));
Une chose que j'ai faite et qui fonctionne à merveille est de lire les deux objets dans HashMap, puis de les comparer à un assertEquals () classique. Elle appellera la méthode equals () de hashmaps, qui comparera de manière récursive tous les objets à l'intérieur (il s'agira d'autres hashmaps ou d'un objet à valeur unique tel qu'une chaîne ou un entier). Cela a été fait en utilisant l'analyseur JSON de Codehaus.
assertEquals(mapper.readValue(expectedJson, new TypeReference<HashMap<String, Object>>(){}), mapper.readValue(actualJson, new TypeReference<HashMap<String, Object>>(){}));
Une approche similaire peut être utilisée si l'objet JSON est plutôt un tableau.
Pour org.json, j'ai déployé ma propre solution, une méthode comparable aux instances de JSONObject. Je n'ai pas travaillé avec des objets JSON complexes dans ce projet, donc je ne sais pas si cela fonctionne dans tous les scénarios. De plus, étant donné que j'utilise ceci dans les tests unitaires, je n'ai pas fait d'effort dans les optimisations. C'est ici:
public static boolean jsonObjsAreEqual (JSONObject js1, JSONObject js2) throws JSONException {
if (js1 == null || js2 == null) {
return (js1 == js2);
}
List<String> l1 = Arrays.asList(JSONObject.getNames(js1));
Collections.sort(l1);
List<String> 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;
}
J'utilise ceci et fonctionne 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<String> keys = obj.keys();
Map<String, Object> 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<Object> jsonSet = new HashSet<>();
for (int i = 0; i < arr.length(); i++) {
jsonSet.add(convertJsonElement(arr.get(i)));
}
return jsonSet;
} else {
return elem;
}
}
}
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] }
(disclaimer: dev ici)
Je prendrais la bibliothèque à http://json.org/Java/ et modifierais la méthode equals
de JSONObject et JSONArray pour faire un test d'égalité profonde. Pour vous assurer que cela fonctionne sans respecter l'ordre des enfants, il vous suffit de remplacer la carte 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;
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;
}
Pour comparer jsons, je recommande d’utiliser 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
Vous pouvez utiliser zjsonpatch library, qui présente les informations de diff conformément à RFC 6902 (correctif JSON). C'est très facile à utiliser. S'il vous plaît visitez sa page de description pour son utilisation
Je sais qu’il n’est généralement pris en compte que pour les tests, mais vous pouvez utiliser le code JSON Hamcrest comparitorSameJSONAs dans JSON Hamcrest.
JSON.areEqual(json1, json2); //using BlobCity Java Commons
https://tech.blobcity.com/2018/09/02/json-equals-in-Java-to-compare-two-jsons
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.
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<String> 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<JsonNode> 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<Entry<String, JsonElement>> ens1 = ((JsonObject) json1).entrySet();
Set<Entry<String, JsonElement>> 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<String, JsonElement> 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;
}