Сравните два объекта JSON в Java

Я ищу библиотеку синтаксического анализа JSON, которая поддерживает сравнение двух объектов JSON, игнорирующих дочерний порядок, в частности для модульного тестирования JSON, возвращающегося из веб-службы.

поддерживает ли это какая-либо из основных библиотек JSON? Организация.библиотека json просто выполняет сравнение ссылок.

22 ответов


в качестве общей архитектурной точки я обычно не советую позволять зависимостям от определенного формата сериализации истекать за пределы вашего уровня хранения / сети; таким образом, я бы сначала рекомендовал вам рассмотреть возможность тестирования равенства между вашими собственными объектами приложения, а не их проявлениями JSON.

сказав это, я в настоящее время большой поклонник Джексон который мой быстрый чтение их ObjectNode.equals () реализация предполагает, что установите сравнение членства, которое вы хотите:

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

попробовать Skyscreamer это JSONAssert.

его нестрогом режим имеет два основных преимущества, которые делают его менее хрупким:

  • расширяемость объекта (например, с ожидаемым значением {id:1}, это все равно пройдет:{id: 1,moredata:'x'}.)
  • порядок свободных массивов (например, ['dog', 'cat']==['cat','dog'])

в строгом режиме он ведет себя больше как тест json-lib класс.

тест выглядит примерно так:

@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);
}

параметры в JSONAssert.assertEquals() вызову expectedJSONString, actualDataString и isStrict.

сообщения результатов довольно ясны, что важно при сравнении действительно больших объектов JSON.


использование 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);

Я бы сделал следующее,

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

вы можете попробовать использовать JSON-lib JSONAssert класс:

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

выдает:

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

если вы уже используете JUnit, последняя версия теперь использует Hamcrest. Это общая структура сопоставления (особенно полезная для модульного тестирования), которая может быть расширена для создания новых сопоставителей.

есть небольшая библиотека с открытым исходным кодом под названием hamcrest-json с совпадениями JSON-aware. Он хорошо документирован, протестирован и поддерживается. Ниже приведены некоторые полезные ссылки:

пример кода с использованием объектов из библиотеки JSON org.json.simple:

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

дополнительно вы можете (1) разрешить массивы "любого порядка" и (2) игнорировать дополнительные поля.

поскольку существует множество библиотек JSON для Java (Jackson, GSON, json-lib, etc.), полезно, что hamcrest-json поддерживает текст JSON (as java.lang.String), а также изначально поддерживающие объекты из JSON Дугласа Крокфорда библиотека org.json.

наконец, если вы не используете JUnit, вы можете использовать Hamcrest непосредственно для утверждений. (я писал об этом здесь.)


используйте эту библиотеку:https://github.com/lukas-krecan/JsonUnit

пом:

<dependency>
    <groupId>net.javacrumbs.json-unit</groupId>
    <artifactId>json-unit</artifactId>
    <version>1.5.0</version>
    <scope>test</scope>
</dependency>

IGNORING_ARRAY_ORDER-игнорирует порядок в массивах

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

Вы можете попробовать JsonUnit. Он может сравнивать два объекта JSON и сообщать о различиях. Он построен на вершине Джексона.

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

результаты

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

одна вещь, которую я сделал, и это творит чудеса, - это прочитать оба объекта в HashMap, а затем сравнить с обычным assertEquals(). Он вызовет метод equals () хэш-карт, который будет рекурсивно сравнивать все объекты внутри (они будут либо другими хэш-картами, либо каким-то одним объектом значения, таким как строка или целое число). Это было сделано с помощью парсера Jackson JSON.

assertEquals(mapper.readValue(expectedJson, new TypeReference<HashMap<String, Object>>(){}), mapper.readValue(actualJson, new TypeReference<HashMap<String, Object>>(){}));

аналогичный подход можно использовать, если объект JSON является массивом.


для org.json я развернул собственное решение, метод, который сравнивается с экземплярами JSONObject. Я не работал со сложными объектами JSON в этом проекте, поэтому я не знаю, работает ли это во всех сценариях. Кроме того, учитывая, что я использую это в модульных тестах, я не прилагал усилий к оптимизации. Вот это:

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

Я использую это, и отлично работает для меня (с орг.формат 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;
        }
    }
}

Я бы взял библиотеку в http://json.org/java/, и измените equals метод JSONObject и JSONArray для выполнения глубокого теста равенства. Чтобы убедиться, что он работает без изменения порядка детей, все, что вам нужно сделать, это заменить внутреннюю карту на TreeMap, или использовать что-то вроде Collections.sort().


попробуйте это:

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

можно использовать zjsonpatch библиотека, которая представляет информацию diff в соответствии с RFC 6902 (патч JSON). Его очень прост в использовании. Пожалуйста, посетите страницу описания, для его использования


Я знаю, что это обычно рассматривается только для тестирования, но вы можете использовать Hamcrest JSON comparitorSameJSONAs в Hamcrest JSON.

Hamcrest JSON SameJSONAs


для таких, как я, желающих сделать это с Джексоном, вы можете использовать в JSON-блок.

JsonAssert.assertJsonEquals(jsonNode1, jsonNode2);

ошибки дают полезную обратную связь по типу несоответствия:

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

каратэ - это именно то, что вы ищете. Вот пример:

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

(отказ от ответственности: dev здесь)


ничто другое, казалось, не работало правильно, поэтому я написал следующее:

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

следующий код будет более полезным для сравнения двух JsonObject, JsonArray, JsonPrimitive и 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;
    }

для сравнения jsons я рекомендую использовать 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

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

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


Это решение для меня, работа очень хорошая:

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<String, String> map = new HashMap<String, String>();         
                        //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

надеюсь, что это поможет другим.