Testing two JSON objects for equality ignoring child order in Java

Viewed 358678

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.

28 Answers

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.

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

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

Hamcrest JSON SameJSONAs

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

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

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

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

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

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

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.

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

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.

Related