A Java library for matching JSONs, with regex, JSONPath, and embedded match tweaks — no glue code required.
Compare any two JSON-convertible Java objects and report every difference on
mismatch. The expected JSON is matched leniently by default — sizes and array
order are ignored, scalar values are matched as Java regular expressions, and
match logic can be tuned with operators (!, !.*, .*, #(...)) embedded
directly inside the expected JSON.
- Fluent builder API:
JSONCompare.compare(expected, actual).modes(...).assertMatches() - Accepts any JSON-convertible input:
JsonNode, JSONString,Map,List, POJO - Regex matching on field names and values, with
\Q…\Equoting to escape literals - Embedded match operators: negation (
!), forbid-extras (!.*), require-extras (.*) - Embedded JSONPath assertions via
#(<jsonpath>)keys, powered by Jayway JsonPath - Soft assertions: every mismatch is reported, not just the first
- Pluggable
JsonComparatorfor custom field/value matching strategies - Throws
org.opentest4j.AssertionFailedError— works with JUnit 5, TestNG, and any opentest4j-aware runner
- Java 17 or later
Maven:
<dependency>
<groupId>com.github.fslev</groupId>
<artifactId>json-compare</artifactId>
<version>${version.from.maven.central}</version>
</dependency>Gradle:
testImplementation 'com.github.fslev:json-compare:<version.from.maven.central>'String expected = """
{
"string": "I'm on a seafood diet. I see food and I eat it!",
"number": "\\\\d+.\\\\d+",
"object": {
"pun": "\\\\QWhy don't skeletons fight each other? They don't have the guts!\\\\E"
},
"array": [".*", "\\\\d+", true, null],
"boolean": "true|false"
}
""";
String actual = """
{
"string": "I'm on a seafood diet. I see food and I eat it!",
"number": 0.99,
"object": {
"pun": "Why don't skeletons fight each other? They don't have the guts!"
},
"array": ["pancake", 18, true, null],
"boolean": true
}
""";
JSONCompare.compare(expected, actual).assertMatches(); // passesEither side may be any JSON-convertible object — Jackson handles the conversion:
Map<String, Object> actualMap = new HashMap<>();
actualMap.put("a", 1);
actualMap.put("b", Arrays.asList("ipsum", 4, 5));
actualMap.put("c", true);
String expected = """
{ "a": 1, "b": [4, "ipsum", "\\\\d+"] }
""";
JSONCompare.compare(expected, actualMap).assertMatches(); // passesThe fluent builder is the only entry point. Configuration methods return
this; the comparison runs only when a terminal method is invoked.
| Builder method | Purpose |
|---|---|
.modes(CompareMode... | Set<…>) |
Tighten default lenient comparison (see below) |
.comparator(JsonComparator) |
Override the default regex-based field/value comparator |
.message(String) |
Append a custom note to assertion failure output |
.assertMatches() (terminal) |
Throw AssertionFailedError if any difference is found |
.assertNotMatches() (terminal) |
Throw AssertionFailedError if the JSONs do match |
.diffs() (terminal) |
Return a List<String> of differences (empty on match) |
The static JSONCompare.assertMatches(...) / assertNotMatches(...) /
diffs(...) overloads still work but are deprecated since 8.0; they delegate
to the builder.
By default, expected JSON only has to be a subset of actual — extra fields
and array elements are tolerated and array order is ignored. The
CompareMode enum tightens this:
JSON_OBJECT_NON_EXTENSIBLE— actual objects must not have extra fieldsJSON_ARRAY_NON_EXTENSIBLE— actual arrays must not have extra elementsJSON_ARRAY_STRICT_ORDER— array elements must match position-for-positionREGEX_DISABLED— compare values and field names by literal equality
String expected = """{ "b": "val1" }""";
String actual = """{ "a": "val2", "b": "val1" }""";
JSONCompare.compare(expected, actual).assertMatches(); // passes (subset)
JSONCompare.compare(expected, actual)
.modes(CompareMode.JSON_OBJECT_NON_EXTENSIBLE)
.assertNotMatches(); // passes (extras present)String expected = """[ "lorem", 2, false ]""";
String actual = """[ false, 2, "lorem", 5, 4 ]""";
JSONCompare.compare(expected, actual).assertMatches(); // passes (order ignored)
JSONCompare.compare(expected, actual)
.modes(CompareMode.JSON_ARRAY_STRICT_ORDER)
.assertNotMatches(); // passes (order differs)Expected scalar values and field names are interpreted as Java regular
expressions (with DOTALL | MULTILINE). Patterns that fail to compile fall
back to literal equality.
JSONCompare.compare("""{ "a": ".*me.*" }""", """{ "a": "some text" }""")
.assertMatches(); // passes
JSONCompare.compare("""{ ".*oba.*": "some value" }""", """{ "foobar": "some value" }""")
.assertMatches(); // passesQuote unintentional regex characters with \Q…\E:
JSONCompare.compare("""{ "a": "\\\\Qd+\\\\E" }""", """{ "a": "d+" }""")
.assertMatches(); // passesFor case-insensitivity, use the inline (?i) / (?-i) flags.
To disable regex globally, use CompareMode.REGEX_DISABLED or supply a custom
JsonComparator:
JSONCompare.compare(expected, actual)
.comparator(new JsonComparator() {
public boolean compareValues(Object expected, Object actual) { return expected.equals(actual); }
public boolean compareFields(String expected, String actual) { return expected.equals(actual); }
})
.assertMatches();Match logic can be embedded directly inside the expected JSON, so assertions can express conditions without writing any matcher code:
| Operator | Meaning |
|---|---|
!<regex> |
DO NOT MATCH — negate the comparison for this value/field |
!.* |
DO NOT MATCH ANY — actual must NOT have extra fields/items |
.* |
MATCH ANY — actual MUST have at least one extra field/item |
#(<jsonpath>) |
Apply a JSONPath query and match the result against the value |
JSONCompare.compare("""{ "a": "!test" }""", """{ "a": "testing" }""")
.assertMatches(); // passes — value does not match "test"
JSONCompare.compare("""{ "!a": "value does not matter" }""", """{ "b": "anything" }""")
.assertMatches(); // passes — actual has no field "a"When negating a field name, the value side is ignored: it asserts only that no actual field with the given name exists at that level.
String expected = """{ "b": "val1", "!.*": ".*" }""";
String actual = """{ "a": "val2", "b": "val1" }""";
JSONCompare.compare(expected, actual).assertNotMatches(); // actual has extra fieldsString expected = """[ false, "test", 4, "!.*" ]""";
String actual = """[ 4, false, "test", 1 ]""";
JSONCompare.compare(expected, actual).assertNotMatches(); // actual has an extra elementString expected = """{ "b": "val1", ".*": ".*" }""";
String actual = """{ "b": "val1" }""";
JSONCompare.compare(expected, actual).assertNotMatches(); // actual has NO extra fieldsA field whose name has the form #(<jsonpath>) runs the path against actual
and matches the result against the field's value:
String expected = """
{
"#($.store..isbn)": [ "0-395-19395-8", "0-553-21311-3", "!.*" ]
}
""";
// actual: a typical { "store": { "book": [ ... ] } } document
JSONCompare.compare(expected, actual).assertMatches();The example asserts the store contains exactly two specific ISBNs — the
trailing "!.*" forbids any further results.
Matching is soft: every mismatch is collected, then reported together.
List<String> diffs = JSONCompare.compare(expected, actual).diffs(); // empty on matchassertMatches() formats the same diffs into an AssertionFailedError:
org.opentest4j.AssertionFailedError: FOUND 4 DIFFERENCE(S):
_________________________DIFF__________________________
$.caught
Expected value: false But got: true
_________________________DIFF__________________________
$.pain.range[1] was not found:
"blue"
_________________________DIFF__________________________
$.pain.range[2] was not found:
-2059921070
_________________________DIFF__________________________
$.pain.not_anyone was not found
Add a custom note with .message("…") — it is appended to the failure
output.
- JTest-Utils — uses json-compare and adds data capture support.
Released under the Apache License 2.0.