A Java library for matching JSONs, with some tweaks !
Compare any JSON convertible Java objects and check the differences between them when matching fails.
The library has some tweaks which helps you make assertions without writing any code at all.
- Junit Jupiter API
- Jackson
- Jayway json-path
<dependency>
<groupId>com.github.fslev</groupId>
<artifactId>json-compare</artifactId>
<version>${version.from.maven.central}</version>
</dependency>
compile 'com.github.fslev:json-compare:<version.from.maven.central>'
JSONCompare will automatically try to convert any given expected or actual Java objects to Jackson JsonNodes and match them.
// expected as String with regex
String expectedString = "{\"a\":1, \"b\": [4, \"ipsum\", \"\\\\d+\"]}";
String actualString = "{\"a\":1, \"b\":[\"ipsum\", 4, 5], \"c\":true}";
JSONCompare.assertMatches(expectedString, actualString); // assertion passes
// actual represented as Map
Map<String, Object> actualMap = new HashMap<>();
actualMap.put("a", 1);
actualMap.put("b", Arrays.asList("ipsum", 4, 5));
actualMap.put("c", true);
JSONCompare.assertMatches(expectedString, actualMap); // assertion passes
String anotherActualString = "{\"a\":2, \"b\":[4, \"lorem\", 5], \"c\":true}";
// Negative assertion
JSONCompare.assertNotMatches(expectedString, anotherActualString); // assertion passes
// Failed assertion
JSONCompare.assertMatches(expectedString, anotherActualString); // assertion fails
==>
org.opentest4j.AssertionFailedError: FOUND 2 DIFFERENCE(S):
_________________________DIFF__________________________
a ->
Expected value: 1 But got: 2
_________________________DIFF__________________________
b ->
Expected element from position 2 was NOT FOUND:
"ipsum"
By default, JSONCompare rules out the Json sizes and also the order of elements from an array. This behaviour can be overridden by using the following compare modes:
- JSON_OBJECT_NON_EXTENSIBLE
- JSON_ARRAY_NON_EXTENSIBLE
- JSON_ARRAY_STRICT_ORDER
Example of JSON_OBJECT_NON_EXTENSIBLE:
// Expected Json is included in actual Json
String expected = "{\"b\": \"val1\"}";
String actual = "{\"a\":\"val2\", \"b\":\"val1\"}";
JSONCompare.assertMatches(expected, actual); // assertion passes
// JSON objects MUST have same sizes
String expected1 = "{\"b\": \"val1\"}";
String actual1 = "{\"a\":\"val2\", \"b\":\"val1\"}";
JSONCompare.assertNotMatches(expected1, actual1, Set.of(CompareMode.JSON_OBJECT_NON_EXTENSIBLE)); // assertion passes
JSONCompare.assertMatches(expected1, actual1, Set.of(CompareMode.JSON_OBJECT_NON_EXTENSIBLE)); // assertion fails
==>
org.opentest4j.AssertionFailedError: FOUND 1 DIFFERENCE(S):
_________________________DIFF__________________________
Actual JSON OBJECT has extra fields
...same for JSON_ARRAY_NON_EXTENSIBLE
Example of JSON_ARRAY_STRICT_ORDER:
// JSON array strict order is by default ignored
String expected = "[\"lorem\", 2, false]";
String actual = "[false, 2, \"lorem\", 5, 4]";
JSONCompare.assertMatches(expected, actual); // assertion passes
// Check JSON array strict order
String expected1 = "[\"lorem\", 2, false]";
String actual1 = "[false, 2, \"lorem\", 5, 4]";
JSONCompare.assertNotMatches(expected1, actual1, Set.of(CompareMode.JSON_ARRAY_STRICT_ORDER)); // assertion passes
JSONCompare.assertMatches(expected1, actual1, Set.of(CompareMode.JSON_ARRAY_STRICT_ORDER)); // assertion fails
==>
org.opentest4j.AssertionFailedError: FOUND 2 DIFFERENCE(S):
_________________________DIFF__________________________
JSON ARRAY elements differ at position 1:
"lorem"
________diffs________
Expected value: "lorem" But got: false
_________________________DIFF__________________________
JSON ARRAY elements differ at position 3:
false
________diffs________
Expected boolean: false But got: "lorem"
You can use regular expressions on expected JSON values:
String expected = "{\"a\": \".*me.*\"}";
String actual = "{\"a\": \"some text\"}";
JSONCompare.assertMatches(expected, actual); // assertion passes
, but also on expected JSON object fields:
String expected = "{\".*oba.*\": \"some value\"}";
String actual = "{\"foobar\": \"some value\"}";
JSONCompare.assertMatches(expected, actual); // assertion passes
JSONCompare is by default case-sensitive and supports regular expressions on JSON fields and values.
If you have unintentional regex characters inside either expected values or expected fields, then you can quote them:
String expected = "{\"a\":\"\\\\Qd+\\\\E\"}";
String actual = "{\"a\":\"d+\"}";
JSONCompare.assertMatches(expected, actual); // assertion passes
By quoting special characters, using \Q and \E, you disable the regex mechanism for that corresponding sequence.
From Java Pattern docs:
\Q Nothing, but quotes all characters until \E
\E Nothing, but ends quoting started by \Q
If you want to enable case-insensitivity, then use (?i)
and (?-i)
modifiers.
However, you can ignore the default regular expression compare mode, by using a custom comparator
String expected = "{\"a\": \"\\\\d+\"}";
String actual = "{\"a\": \"\\\\d+\"}";
JSONCompare.assertMatches(expected, actual, new JsonComparator() {
public boolean compareValues(Object expected, Object actual) {
return expected.equals(actual);
}
public boolean compareFields(String expected, String actual) {
return expected.equals(actual);
}
}); // assertion passes
OR, use CompareMode.REGEX_DISABLED:
String expected = "{\"a\":\"(some value)\"}";
String actual = "{\"a\":\"(some value)\"}";
JSONCompare.assertMatches(expected, actual, new HashSet<>(Collections.singletonList(CompareMode.REGEX_DISABLED))); // assertion passes
JSONCompare.assertMatches(expected, actual); // assertion fails
Matching is based on soft assertion. It does not stop at first encountered difference, but it continues until expected JSON is depleted.
All differences are displayed via the AssertionError message from JSONCompare.assertMatches()
or can be obtained as a List of Strings by using JSONCompare.diffs()
:
String expected = "{\n" +
" \"caught\": false,\n" +
" \"pain\": {\n" +
" \"range\": [\n" +
" \"bell\",\n" +
" \"blue\",\n" +
" -2059921070\n" +
" ],\n" +
" \"not_anyone\": -1760889549.4041045,\n" +
" \"flat\": -2099670336\n" +
" }\n" +
"}";
String actual = "{\n" +
" \"caught\": true,\n" +
" \"pain\": {\n" +
" \"range\": [\n" +
" \"bell\",\n" +
" \"red\",\n" +
" -2059921075\n" +
" ],\n" +
" \"anyone\": -1760889549.4041045,\n" +
" \"flat\": -2099670336\n" +
" },\n" +
" \"broad\": \"invented\"\n" +
"}";
JSONCompare.assertMatches(expected, actual); // assertion fails
org.opentest4j.AssertionFailedError: FOUND 4 DIFFERENCE(S):
_________________________DIFF__________________________
caught ->
Expected value: false But got: true
_________________________DIFF__________________________
pain -> range ->
Expected element from position 2 was NOT FOUND:
"blue"
_________________________DIFF__________________________
pain -> range ->
Expected element from position 3 was NOT FOUND:
-2059921070
_________________________DIFF__________________________
pain -> Field 'not_anyone' was NOT FOUND
Json matching by default uses regular expressions.
In case expected json contains any unintentional regexes, then quote them between \Q and \E delimiters or use a custom comparator.
==>
<Click to see difference>
List<String> diffs = JSONCompare.diffs(expected, actual);
JSONCompare has some tweaks which help you to fine tune the matching mechanism.
These tweaks can be directly embedded inside the expected JSON, thus you don't have to write any code at all:
- DO_NOT_MATCH
!
- DO_NOT_MATCH_ANY
!.*
- MATCH_ANY
.*
- JSON PATH expressions
#(..)
By using the !
DO NOT MATCH option, the comparison between JSON values will be negated:
String expected = "{\"a\": \"!test\"}";
String actual = "{\"a\": \"testing\"}";
JSONCompare.assertMatches(expected, actual); // assertion passes
or, between JSON object fields
String expected = "{\"!a\": \"value does not matter\"}";
String actual = "{\"b\": \"of course value does not matter\"}";
JSONCompare.assertMatches(expected, actual); // assertion passes
Negating a field name, it means that the actual JSON object should not have any field with same name on same level. In this particular case, field values are ignored.
Of course, you can use negative lookahead or lookbehind regular expressions
String expected = "{\"(?!lorem.*).*\": \"valorem\"}";
String actual = "{\"ipsum\": \"valorem\"}";
JSONCompare.assertMatches(expected, actual); // assertion passes
The assertion will pass if the actual JSON has a field which does not contain 'lorem' and which points to value 'valorem'.
Same goes for JSON arrays.
Check for extra JSON values or fields by using the power of regex
and DO_NOT_MATCH_ANY use case
// actual JSON object should NOT contain any extra fields
String expected = "{\"b\": \"val1\", \"!.*\": \".*\"}";
String actual = "{\"a\": \"val2\", \"b\": \"val1\"}";
JSONCompare.assertMatches(expected, actual); //assertion fails
==>
org.opentest4j.AssertionFailedError: FOUND 1 DIFFERENCE(S):
_________________________DIFF__________________________
Expected condition '!.*' was not met. Actual JSON OBJECT has extra fields
// actual JSON array should NOT contain any extra elements
String expected = "[false, \"test\", 4, \"!.*\"]";
String actual = "[4, false, \"test\", 1]";
JSONCompare.assertNotMatches(expected, actual); // assertion fails
==>
org.opentest4j.AssertionFailedError: FOUND 1 DIFFERENCE(S):
_________________________DIFF__________________________
Expected condition "!.*" from position 4 was not met. Actual JSON ARRAY has extra elements.
Check actual Json should have extra fields or elements.
// Actual JSON object should have extra fields
String expected = "{\"b\": \"val1\", \".*\": \".*\"}";
String actual = "{\"b\": \"val1\"}";
JSONCompare.assertMatches(expected, actual); // assertion fails
==>
org.opentest4j.AssertionFailedError: FOUND 1 DIFFERENCE(S):
_________________________DIFF__________________________
Field '.*' was NOT FOUND
// Actual JSON array should have extra elements
String expected = "[false, \"test\", 4, \".*\"]";
String actual = "[4, false, \"test\"]";
JSONCompare.assertMatches(expected, actual); // assertion fails
==>
org.opentest4j.AssertionFailedError: FOUND 1 DIFFERENCE(S):
_________________________DIFF__________________________
Expected condition ".*" from position 4 was not met. Actual JSON ARRAY has no extra elements.
Powered by JsonPath
The expected JSON can contain json path expressions delimited by #(
and )
together with the expected results:
// Select the 'isbn' values of store books and match with expected ones
String expected = "{\"#($.store..isbn)\":[\"0-395-19395-8\", \"0-553-21311-1\", \"!.*\"]}";
String actual = "{\n" +
" \"store\": {\n" +
" \"book\": [\n" +
" {\n" +
" \"category\": \"reference\",\n" +
" \"author\": \"Nigel Rees\",\n" +
" \"title\": \"Sayings of the Century\",\n" +
" \"price\": 8.95\n" +
" },\n" +
" {\n" +
" \"category\": \"fiction\",\n" +
" \"author\": \"Herman Melville\",\n" +
" \"title\": \"Moby Dick\",\n" +
" \"isbn\": \"0-553-21311-3\",\n" +
" \"price\": 8.99\n" +
" },\n" +
" {\n" +
" \"category\": \"fiction\",\n" +
" \"author\": \"J. R. R. Tolkien\",\n" +
" \"title\": \"The Lord of the Rings\",\n" +
" \"isbn\": \"0-395-19395-8\",\n" +
" \"price\": 22.99\n" +
" }\n" +
" ]\n" +
" }\n" +
"}";
JSONCompare.assertMatches(expected, actual); // assertion fails
==>
org.opentest4j.AssertionFailedError: FOUND 1 DIFFERENCE(S):
_________________________DIFF__________________________
Json path '$.store..isbn' -> Expected json path result:
["0-395-19395-8","0-553-21311-1","!.*"]
But got:
["0-553-21311-3","0-395-19395-8"]
________diffs________
Expected element from position 2 was NOT FOUND:
"0-553-21311-1"
You might be also interested in looking into JTest-Utils which uses JSONCompare with data capture support: https://github.com/fslev/jtest-utils?tab=readme-ov-file#17-match-and-capture