Testing two JSON objects for equality ignoring child order in Java

JavaJsonJunit

Java Problem Overview


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.

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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

Solution 3 - Java

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

Solution 4 - Java

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

Solution 5 - Java

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]}");

Solution 6 - Java

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

Solution 7 - Java

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

Solution 8 - Java

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.

Solution 9 - Java

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

Solution 10 - Java

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.

Solution 11 - Java

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

Solution 12 - Java

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

Solution 13 - Java

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

Hamcrest JSON SameJSONAs

Solution 14 - Java

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

Solution 15 - Java

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

Solution 16 - Java

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)

Solution 17 - Java

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

Solution 18 - Java

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!!!

Solution 19 - Java

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

Solution 20 - Java

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.

Solution 21 - Java

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

Solution 22 - Java

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

Solution 23 - Java

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

Solution 24 - Java

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

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

Solution 25 - Java

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

Solution 26 - Java

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.

Solution 27 - Java

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

Solution 28 - Java

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJeffView Question on Stackoverflow
Solution 1 - JavaCarter PageView Answer on Stackoverflow
Solution 2 - JavaJolly RogerView Answer on Stackoverflow
Solution 3 - JavaaxelhzfView Answer on Stackoverflow
Solution 4 - JavajoshView Answer on Stackoverflow
Solution 5 - JavachethuView Answer on Stackoverflow
Solution 6 - JavahertzsprungView Answer on Stackoverflow
Solution 7 - JavakevinarpeView Answer on Stackoverflow
Solution 8 - JavaLukasView Answer on Stackoverflow
Solution 9 - JavacatcherView Answer on Stackoverflow
Solution 10 - JavaClaudio AguiarView Answer on Stackoverflow
Solution 11 - JavaVictor IonescuView Answer on Stackoverflow
Solution 12 - JavaGopi VishwakarmaView Answer on Stackoverflow
Solution 13 - JavaJustin OhmsView Answer on Stackoverflow
Solution 14 - JavaYoniView Answer on Stackoverflow
Solution 15 - JavaMishaView Answer on Stackoverflow
Solution 16 - JavaPeter ThomasView Answer on Stackoverflow
Solution 17 - JavaSlev FlorinView Answer on Stackoverflow
Solution 18 - JavaAlikElzin-kilakaView Answer on Stackoverflow
Solution 19 - JavaJulian CardenasView Answer on Stackoverflow
Solution 20 - JavakrookedkingView Answer on Stackoverflow
Solution 21 - JavaJanez KuharView Answer on Stackoverflow
Solution 22 - JavaAlex RView Answer on Stackoverflow
Solution 23 - JavaRadadiya NikunjView Answer on Stackoverflow
Solution 24 - JavaSanket SarangView Answer on Stackoverflow
Solution 25 - JavalikejudoView Answer on Stackoverflow
Solution 26 - JavaMattias Isegran BerganderView Answer on Stackoverflow
Solution 27 - JavaSaikatView Answer on Stackoverflow
Solution 28 - JavaAshley FriezeView Answer on Stackoverflow