web-dev-qa-db-fra.com

Test d'égalité de deux objets JSON en ignorant l'ordre des enfants en Java

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.

191
Jeff

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;
}
72
Jolly Roger

Essayez Skyscreamer JSONAssert .

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

  • Extensibilité de l'objet (par exemple, avec une valeur attendue de {id: 1}, cela passerait quand même: {id: 1, moredata: 'x'}.)
  • Ordre de tableau en vrac (par exemple, ['chien', 'chat'] == ['chat', 'chien'])

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.

130
Carter Page

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);
38
axelhzf

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);
21
joshu

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]>
12
hertzsprung

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.
11
Lukas

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. )

11
kevinarpe

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));
11
chethu

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.

6
Claudio Aguiar

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;
}
4
Victor Ionescu

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;
        }
    }
}
4
catcher

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)

2
Peter Thomas

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

2
Yoni

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;
    }
2
Misha

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
1
Slev Florin

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

1
z31415

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.

Hamcrest JSON SameJSONAs

1
Justin Ohms
JSON.areEqual(json1, json2); //using BlobCity Java Commons

https://tech.blobcity.com/2018/09/02/json-equals-in-Java-to-compare-two-jsons

0
Sanket Sarang

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.
0
krookedking

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;
}
0
Alex R

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;
    }
0
Radadiya Nikunj