291

I'm looking for a JSON parsing library that supports comparing two JSON objects ignoring child order, specifically for unit testing JSON returning from a web service.

Do any of the major JSON libraries support this? The org.json library simply does a reference comparison.

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
Jeff
  • 5,746
  • 4
  • 33
  • 40
  • 1
    Can't serialize both objects to string representation and compare? I guess all of the libraries support `toString()` to convert the object to `JSON` string. – Teja Kantamneni Feb 12 '10 at 17:33
  • 57
    That assumes that order on serialization to and from strings are always the same. I'm not comfortable making that assumption. – Jeff Feb 12 '10 at 18:46
  • You're right Jeff, it's not safe at all. This test shows a scenario where the mappings are the same but toString() does not return the same output: https://gist.github.com/anonymous/5974797. This is because the underlying HashMap can grow, and if you remove keys, the HashMap internal array does not shrink. – Guillaume Perrot Jul 11 '13 at 11:52
  • Json Compare [json-compare](https://github.com/fslev/json-compare) – Slev Florin Jan 04 '21 at 14:46

28 Answers28

194

Try Skyscreamer's JSONAssert.

Its non-strict mode has two major advantages that make it less brittle:

  • Object extensibility (e.g. With an expected value of {id:1}, this would still pass: {id:1,moredata:'x'}.)
  • Loose array ordering (e.g. ['dog','cat']==['cat','dog'])

In strict mode it behaves more like json-lib's test class.

A test looks something like this:

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

The parameters in the JSONAssert.assertEquals() call are expectedJSONString, actualDataString, and isStrict.

The result messages are pretty clear, which is important when comparing really big JSON objects.

Carter Page
  • 2,645
  • 3
  • 18
  • 14
  • 28
    I've been using this solution, but I've just found that you could also provide a JSONCompareMode of you choosing. One of which is NON_EXTENSIBLE. So you'd have something like this: `JSONAssert.assertEquals(expected, data, JSONCompareMode.NON_EXTENSIBLE);` The NON_EXTENSIBLE mode means that any new or missing fields cause failures, but the order doesn't. Using false would instigate lenient mode which wouldn't report any extra or missing child elements. – Dan Temple Nov 05 '13 at 15:38
  • 1
    The NON_EXTENSIBLE compare mode is *exactly* what I was looking for. Thanks for this, Dan. – ThoughtCrhyme Dec 13 '13 at 18:51
  • 3
    Before I get all excited: does this support nested JSON objects and arrays as well? :) – Christian Mar 10 '14 at 18:19
  • First of all , JSONAssert works great in most of the cases:). However, in the case of arrays in json elements, the error message does not look very explanatory.Looks like some error messages can be improved.Is that being considered? Is this project still active? – nikel Jul 10 '15 at 01:14
  • It's still active, though largely in maintenance mode. If someone offers a pull request to improve the error messages, I'm happy to review it. – Carter Page Jul 10 '15 at 15:25
  • 2
    People might want to take [this JSONassert issue](https://github.com/skyscreamer/JSONassert/issues/44) into account before using the library: it indicates that there are currently potential licensing issues with a dependency of the library. – Chriki Sep 08 '15 at 13:22
  • What is the method "getRESTData"? – Peters_ Feb 23 '16 at 09:01
  • 3
    JSONAssert issue #44 fixed with clean-room library replacement in PR 67, released today as JSONAssert 1.4.0. – Carter Page Oct 30 '16 at 22:00
  • I'd like to mention [Karate](https://github.com/intuit/karate) as an alternative to JSONAssert - and it is specialized for testing REST-API responses. (disclaimer: am dev). – Peter Thomas Feb 25 '17 at 04:08
  • Error: Cannot resolve method 'getRESTData'. How can I resolve this issue, please? – Testilla Nov 15 '17 at 14:18
  • 1
    Abandonware. Did not work and tried emailing them. `The following message to was undeliverable. The reason for the problem: 5.1.0 - Unknown address error 550-"5.1.1 The email account that you tried to reach does not exist. ` – likejudo May 27 '20 at 16:35
  • @CarterPage can you please see my post? https://stackoverflow.com/questions/62048635/finding-json-diff-fails-using-jsonassert I tried emailing them. The following message to was undeliverable. The reason for the problem: 5.1.0 - Unknown address error 550-"5.1.1 The email account that you tried to reach does not exist. – likejudo May 27 '20 at 18:21
103

As a general architectural point, I usually advise against letting dependencies on a particular serialization format bleed out beyond your storage/networking layer; thus, I'd first recommend that you consider testing equality between your own application objects rather than their JSON manifestations.

Having said that, I'm currently a big fan of Jackson which my quick read of their ObjectNode.equals() implementation suggests does the set membership comparison that you want:

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;
}
Sled
  • 18,541
  • 27
  • 119
  • 168
Jolly Roger
  • 3,913
  • 4
  • 24
  • 23
  • This method is not symmetric, as it only tests 'subset' relationship of the children, not equality. the 'other' object may have more children then in _children, and this method would still return true. – Yoni Feb 13 '10 at 07:28
  • 24
    @Yoni: Not true, as there's a size comparison. They must have the exact same number of children as well as the same children. @Jolly Roger: In this case, I'm not serializing the object from JSON back into a POJO, but when sending JSON a system that does, I can't rely on it sending it back in the exact same format in which I sent it. – Jeff Feb 15 '10 at 16:27
  • 1
    @Jeff Did this work for you? In junit, assertEquals fails for me. The project is using an old version (1.5), fyi. – ronnyfm Jul 06 '15 at 17:06
  • 1
    I think you're missing some high level testing if you aren't asserting JSON. You could have a thin test which makes a http request and responds with some expected JSON - it's the main functional behaviour of the web service. – mogronalol Sep 07 '16 at 09:13
  • 1
    Just a minor note regarding performance: the code that compares 'value' with 'otherValue' can be simplified to if (value.equals(other.get(key)) return false; as 'value' is guaranteed to be not-null and JsonNode's equals() method ought to accept a null argument. – Tillmann Dec 02 '16 at 10:48
61

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

Edit: Since GSON v2.8.6 the instance method JsonParser.parse is deprecated. You have to use the static method JsonParser.parseString:

JsonElement o1 = JsonParser.parseString("{a : {a : 2}, b : 2}");
JsonElement o2 = JsonParser.parseString("{b : 2, a : {a : 2}}");
assertEquals(o1, o2);
GabrielBB
  • 2,479
  • 1
  • 35
  • 49
axelhzf
  • 993
  • 1
  • 10
  • 8
43

I would do the following,

JSONObject obj1 = /*json*/;
JSONObject obj2 = /*json*/;

ObjectMapper mapper = new ObjectMapper();

JsonNode tree1 = mapper.readTree(obj1.toString());
JsonNode tree2 = mapper.readTree(obj2.toString());

return tree1.equals(tree2);
Jared Burrows
  • 54,294
  • 25
  • 151
  • 185
josh
  • 13,793
  • 12
  • 49
  • 58
  • If you're using Jackson already this is the best answer IMHO. – Christoph Dietze Mar 06 '16 at 10:54
  • 26
    but this is an strict comparison I think. It will return false for two equal json having different order of elements. – deadpool May 05 '16 at 12:43
  • @deadpool the principle is sound, you'd just need to change the comparison to be more involved (checking for key fields for example rather than a simple equals of the trees). – jwenting Apr 20 '17 at 10:46
  • This is the best answer ever. Not only it answers my question now, as it also answers almost every object comparison we need to do. thanks, joshu. – Luiz Feijão Veronesi Aug 31 '17 at 02:14
  • 2
    @deadpool, it may have been a strict comparison, but it is not strict now. – Andrei Damian-Fekete Mar 28 '18 at 12:43
  • 3
    This don't work when you have array as nested object and there is difference in order in array – sourabh Jan 07 '19 at 12:00
  • 3
    This will ignore most order changes except for list order changes that must be the same. see here: https://www.baeldung.com/jackson-compare-two-json-objects – thedrs Jun 09 '20 at 06:29
  • in `jackson:2.14.2` order is important. see `com.fasterxml.jackson.databind.node.ArrayNode.equals` – ssz Jun 28 '23 at 15:55
23

Use this library: https://github.com/lukas-krecan/JsonUnit

Pom:

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

IGNORING_ARRAY_ORDER - ignores order in arrays

assertThatJson("{\"test\":[1,2,3]}")
  .when(Option.IGNORING_ARRAY_ORDER)
  .isEqualTo("{\"test\": [3,2,1]}");
np_6
  • 514
  • 1
  • 6
  • 19
chethu
  • 341
  • 2
  • 4
20

You could try using json-lib's JSONAssert class:

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

Gives:

junit.framework.ComparisonFailure: objects differed at key [baz]; expected:<[qux]> but was:<[xyzzy]>
Jared Burrows
  • 54,294
  • 25
  • 151
  • 185
hertzsprung
  • 9,445
  • 4
  • 42
  • 77
  • Or even simpler: JSONAssert.assertJsonEquals( "{foo: 'bar', baz: 'qux'}", {foo: 'bar', baz: 'xyzzy'}"); – Rob Juurlink Jan 22 '13 at 09:45
  • 3
    Whil this solution works for the order of data items within the JSON, it will fail if the order of elements within arrays does not match. If your code uses a Set that is converted to JSON for example. The following JSON compare would fail: `JSONAssert.assertJsonEquals( "{foo: 'bar', list: [{test: '1'}, {rest: '2'}] }", "{ foo: 'bar', list: [{rest: '2'}, {test: '1'}] }");` With the message: `junit.framework.AssertionFailedError: : : objects differed at key [list];: arrays first differed at element [0];: objects differed at key [test];` – Dan Temple Oct 31 '13 at 11:59
  • Yes, this is a limitation of Json :(. json.org shows that there is no unordered collection token. { } can only surround key-value pairs. This is highly irritating – Merk Jan 28 '16 at 02:20
14

If you are already using JUnit, the latest version now employs Hamcrest. It is a generic matching framework (especially useful for unit testing) that can be extended to build new matchers.

There is a small open source library called hamcrest-json with JSON-aware matches. It is well documented, tested, and supported. Below are some useful links:

Example code using objects from the JSON library org.json.simple:

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

Optionally, you may (1) allow "any-order" arrays and (2) ignore extra fields.

Since there are a variety of JSON libraries for Java (Jackson, GSON, json-lib, etc.), it is useful that hamcrest-json supports JSON text (as java.lang.String), as well as natively supporting objects from Douglas Crockford's JSON library org.json.

Finally, if you are not using JUnit, you can use Hamcrest directly for assertions. (I wrote about it here.)

Community
  • 1
  • 1
kevinarpe
  • 20,319
  • 26
  • 127
  • 154
13

You can try JsonUnit. It can compare two JSON objects and report differences. It's built on top of Jackson.

For example

assertThatJson("{\"test\":1}").isEqualTo("{\n\"test\": 2\n}");

Results in

java.lang.AssertionError: JSON documents are different:
Different value found in node "test". Expected 1, got 2.
Lukas
  • 13,606
  • 9
  • 31
  • 40
8

I'm using this, and works fine for me (with 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;
        }
    }
}
catcher
  • 101
  • 1
  • 5
  • This method just compares the objects, but doesn't give a reasonable explanation what the difference is. – LoBo Jan 03 '23 at 11:00
8

One thing I did and it works wonders is to read both objects into HashMap and then compare with a regular assertEquals(). It will call the equals() method of the hashmaps, which will recursively compare all objects inside (they will be either other hashmaps or some single value object like a string or integer). This was done using Codehaus' Jackson JSON parser.

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

A similar approach can be used if the JSON object is an array instead.

4

For org.json I've rolled out my own solution, a method that compares to JSONObject instances. I didn't work with complex JSON objects in that project, so I don't know whether this works in all scenarios. Also, given that I use this in unit tests, I didn't put effort into optimizations. Here it is:

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;
}
Victor Ionescu
  • 1,967
  • 2
  • 21
  • 24
  • 1
    Since you mentioned optimizations :), if `val1` is null you will get a NullPointerException from this code `if (!val1.equals(val2)) {` – JohnDoDo Jul 23 '12 at 12:28
  • It happens to the best of us also :). +1 for fixing it in your answer. – JohnDoDo Jul 24 '12 at 07:17
  • point taken :) Hope that it doesn't get too much votes though, otherwise it's gonna be lots of support. – Victor Ionescu Jul 24 '12 at 11:00
  • 1
    You did not consider if `js2` is `null` or not when `js1` is not `null` – Xiao Jul 11 '13 at 08:54
  • This code does not work for nested objects/sequences. – FabienB Oct 29 '14 at 16:14
  • I have nested objects and I simply added one more check `else if (!(val1 instanceof JSONObject) && !val1.equals(val2)) { return false; }`. Not really good but works for me. – kechap Jan 09 '15 at 07:41
3

For those like me wanting to do this with Jackson, you can use json-unit.

JsonAssert.assertJsonEquals(jsonNode1, jsonNode2);

The errors give useful feedback on the type of mismatch:

java.lang.AssertionError: JSON documents have different values:
Different value found in node "heading.content[0].tag[0]". Expected 10209, got 10206.
krookedking
  • 2,203
  • 20
  • 21
3

You can use zjsonpatch library, which presents the diff information in accordance with RFC 6902 (JSON Patch). Its very easy to use. Please visit its description page for its usage

3

I know it is usually considered only for testing but you could use the Hamcrest JSON comparitorSameJSONAs in Hamcrest JSON.

Hamcrest JSON SameJSONAs

Justin Ohms
  • 3,334
  • 31
  • 45
3

For comparing jsons I recommend using my library, 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
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Slev Florin
  • 149
  • 1
  • 6
3

Do any of the major JSON libraries support this? The org.json library simply does a reference comparison.

But org.json does support this! Use similar() instead of equals().

Janez Kuhar
  • 3,705
  • 4
  • 22
  • 45
3

This may help those working with Spring Framework. You may reuse what is used internally for doing assertions on ResultActions (for controller testing):

Import: org.springframework.test.util.JsonExpectationsHelper

And you can write tests that break with verbose output:

java.lang.AssertionError: someObject.someArray[1].someInternalObject2.value
Expected: 456
     got: 4567

Test code:

@Test
void test() throws Exception {

    final String json1 =
        "{" +
        "  'someObject': {" +
        "    'someArray': [" +
        "      {" +
        "        'someInternalObject': {" +
        "          'value': '123'" +
        "        }" +
        "      }," +
        "      {" +
        "        'someInternalObject2': {" +
        "          'value': '456'" +
        "        }" +
        "      }" +
        "    ]" +
        "  }" +
        "}";

    final String json2 =
        "{" +
        "  'someObject': {" +
        "    'someArray': [" +
        "      {" +
        "        'someInternalObject': {" +
        "          'value': '123'" +
        "        }" +
        "      }," +
        "      {" +
        "        'someInternalObject2': {" +
        "          'value': '4567'" +
        "        }" +
        "      }" +
        "    ]" +
        "  }" +
        "}";

    new JsonExpectationsHelper().assertJsonEqual(json1, json2, true);
}
Julian Cardenas
  • 4,897
  • 2
  • 10
  • 10
  • 1
    Many thanks! Direct usage of [JSONassert](https://jsonassert.skyscreamer.org/) library was disallowed by my management. However, JsonExpectationsHelper uses it :) – LoBo Jan 03 '23 at 10:27
2

Try this:

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;
    }
user987339
  • 10,519
  • 8
  • 40
  • 45
Misha
  • 21
  • 1
  • within jsonArrays this returns true if *some* of the elements coincide, as opposed to *all* the elements coincide. – matiasg Jul 18 '14 at 20:17
  • @matiasg - does the `if (obj1Array.length() != obj2Array.length())` not ensure *all* elements coincide? – kwah Aug 19 '14 at 12:44
  • 3
    @kwah: nope. Consider this example: obj1Array = [1,1,1], obj2Array = [1,2,3]. This would return true. Also, even if elements coincide, they should be in the same order. This would return true for [1,2,3] and [2,3,1] too, which is wrong – matiasg Aug 19 '14 at 16:11
2

I'd take the library at http://json.org/java/, and modify the equals method of JSONObject and JSONArray to do a deep equality test. To make sure that it works regradless of the order of the children, all you need to do is replace the inner map with a TreeMap, or use something like Collections.sort().

Yoni
  • 10,171
  • 9
  • 55
  • 72
  • 4
    its not that great - it really should have come with code to do json comparison. – Chii Nov 23 '10 at 06:59
  • But imagine writing that code where JSON can be anything in any structure...write the compare on that! Its like writing a compare for all types HTML pages. – JPM Nov 09 '11 at 17:53
2

Karate is exactly what you are looking for. Here is an example:

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

(disclaimer: dev here)

Peter Thomas
  • 54,465
  • 21
  • 84
  • 248
2

What I did is converting the jsons into maps using gson and comparing the maps using assertj:

Map<Object, Object> resMap = gson.fromJson(res, new TypeToken<Map<Object, Object>>() {}.getType());
Map<Object, Object> expectedMap = gson.fromJson(expected, new TypeToken<Map<Object, Object>>() {}.getType());
Assertions.assertThat(resMap).usingRecursiveComparison().isEqualTo(expectedMap);

The result is a nice comparison between all attributes, recursively!!!

AlikElzin-kilaka
  • 34,335
  • 35
  • 194
  • 277
1

Here is the code using Jackson ObjectMapper. To know more read this article.

import com.fasterxml.jackson.*

boolean compareJsonPojo(Object pojo1, Object pojo2) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            String str1 = mapper.writeValueAsString(pojo1);
            String str2 = mapper.writeValueAsString(pojo2);
            return mapper.readTree(str1).equals(mapper.readTree(str2));
        } catch (JsonProcessingException e) {
            throw new AssertionError("Error comparing JSON objects: " + e.getMessage());
        }
    }
Saikat
  • 14,222
  • 20
  • 104
  • 125
0

Nothing else seemed to work quite right, so I wrote this:

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;
}
Alex R
  • 11,364
  • 15
  • 100
  • 180
0

Following code will be more helpful to compare two JsonObject, JsonArray, JsonPrimitive and 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;
    }
Radadiya Nikunj
  • 988
  • 11
  • 10
0
JSON.areEqual(json1, json2); //using BlobCity Java Commons

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

0

Looking at the answers, I tried JSONAssert but it failed. So I used Jackson with zjsonpatch. I posted details in the SO answer here.

likejudo
  • 3,396
  • 6
  • 52
  • 107
0

toMap() in JSONObject works fine with nested objects and arrays already.

As the java.util.Map interface specifies to check the mappings and not the order, comparing the Maps is fine and also recursive.

json1 = new JSONObject("{...}");
json2 = new JSONObject("{...}");
json1.toMap().equals(json2.toMap());

It will work fine with any order and nested elements.

It will NOT however work with extra/ignored elements. If those are known you can remove them before calling equals on the maps.

Mattias Isegran Bergander
  • 11,811
  • 2
  • 41
  • 49
0

ModelAssert - https://github.com/webcompere/model-assert does this. By default, it prefers the JSON to be in order, but it can use relaxed order of object keys and array elements:

assertJson(json1)
   .where().keysInAnyOrder().arrayInAnyOrder()
   .isEqualTo(json2);

This assertion is AssertJ style - i.e. using a fluent DSL. ModelAssert can also be used to build Hamcrest or Mockito matchers with the same DSL.

The Json can be a String, File, Jackson JsonNode, or even a POJO that is spontaneously converted to JSON for comparison.

There's also support for yml.

Ashley Frieze
  • 4,993
  • 2
  • 29
  • 23