Skip to content

Repository files navigation

Java json-diff

A customizable library to perform JSON comparisons with detailed diff output.

Why Use json-diff?

This library provides:

  • Readable diffs between two JSON documents
  • Similarity scoring (0-100) to compare multiple JSON documents and find the most similar ones
  • Fully customizable comparison modes (strict, lenient, or mixed) with easy-to-create custom matchers
  • Multiple output formats (patch file, text) with the ability to create custom formatters

Installation

Maven:

<dependency>
<groupId>io.github.deblockt</groupId>
<artifactId>json-diff</artifactId>
<version>2.0.0</version>
</dependency>

Gradle:

implementation 'io.github.deblockt:json-diff:2.0.0'

Note: Version 2.0.0 requires Java 21+ and uses Jackson 3.x

Quick Start

finalvarexpectedJson = "{\"name\": \"John\", \"age\": 30, \"city\": \"Paris\"}";
finalvarreceivedJson = "{\"name\": \"Jane\", \"age\": 30, \"country\": \"France\"}";
// Define your matcherfinalvarjsonMatcher = newCompositeJsonMatcher(
newLenientJsonArrayPartialMatcher(),
newLenientJsonObjectPartialMatcher(),
newStrictPrimitivePartialMatcher()
);
// Generate the difffinalvardiff = DiffGenerator.diff(expectedJson, receivedJson, jsonMatcher);
// Display errorsSystem.out.println(OnlyErrorDiffViewer.from(diff));
// Get similarity score (0-100)System.out.println("Similarity: " + diff.similarityRate() + "%");

Output:

The property "$.city" is not found
The property "$.name" didn't match. Expected "John", Received: "Jane"
Similarity: 50.0%

Output Formats

Error List (OnlyErrorDiffViewer)

finalvarerrors = OnlyErrorDiffViewer.from(diff);
System.out.println(errors);

Output:

The property "$.city" is not found
The property "$.name" didn't match. Expected "John", Received: "Jane"

Patch Format (PatchDiffViewer)

finalvarpatch = PatchDiffViewer.from(diff);
System.out.println(patch);

Output:

--- actual+++ expected@@ @@
{
"age": 30,
+ "city": "Paris",- "country": "France",- "name": "Jane",+ "name": "John"
}

Comparison Modes

CompositeJsonMatcher accepts multiple matchers that handle different JSON types. The order matters: the first matcher that can handle a comparison will be used.

Strict Mode

Requires exact matches:

finalvarstrictMatcher = newCompositeJsonMatcher(
newStrictJsonArrayPartialMatcher(), // Same items in same ordernewStrictJsonObjectPartialMatcher(), // Same properties, no extrasnewStrictPrimitivePartialMatcher() // Exact type and value match
);

Lenient Mode

Ignores extra properties and array order:

finalvarlenientMatcher = newCompositeJsonMatcher(
newLenientJsonArrayPartialMatcher(), // Ignores array order and extra itemsnewLenientJsonObjectPartialMatcher(), // Ignores extra propertiesnewLenientNumberPrimitivePartialMatcher(), // 10.0 == 10newStrictPrimitivePartialMatcher() // Other primitives
);

Mixed Mode

You can combine matchers for custom behavior:

finalvarmixedMatcher = newCompositeJsonMatcher(
newLenientJsonArrayPartialMatcher(), // Lenient on arraysnewStrictJsonObjectPartialMatcher(), // Strict on objectsnewStrictPrimitivePartialMatcher()
);

Available Matchers

Array Matchers

MatcherDescription
LenientJsonArrayPartialMatcherIgnores array order and extra items
StrictJsonArrayPartialMatcherRequires same items in same order

Object Matchers

MatcherDescription
LenientJsonObjectPartialMatcherIgnores extra properties in received JSON
StrictJsonObjectPartialMatcherRequires exact same properties

Primitive Matchers

MatcherDescription
StrictPrimitivePartialMatcherExact type and value match
LenientNumberPrimitivePartialMatcherNumbers are equal if values match (10.0 == 10)

Special Matchers

MatcherDescription
NullEqualsEmptyArrayMatcherTreats null and [] as equivalent
IgnoredPathMatcherIgnores specified fields during comparison

Treating Null as Empty Array

The NullEqualsEmptyArrayMatcher allows you to consider null values and empty arrays [] as equivalent. This is useful when different systems represent "no data" differently.

finalvarjsonMatcher = newCompositeJsonMatcher(
newNullEqualsEmptyArrayMatcher(), // Must be first to handle null vs []newLenientJsonArrayPartialMatcher(),
newLenientJsonObjectPartialMatcher(),
newStrictPrimitivePartialMatcher()
);
// These will match with 100% similarity:// {"items": null} vs {"items": []}// {"items": []} vs {"items": null}finalvardiff = DiffGenerator.diff(
"{\"items\": null}",
"{\"items\": []}",
jsonMatcher
);
System.out.println(diff.similarityRate()); // 100.0

Important:

  • Place NullEqualsEmptyArrayMatcherbefore other matchers in the constructor
  • This matcher only handles null vs empty array [], not missing properties
  • Non-empty arrays do not match null

Ignoring path

The IgnoredPathMatcher allows you to ignore specific fields during comparison. This is useful for fields like timestamps, IDs, or other dynamic values that you don't want to compare.

finalvarjsonMatcher = newCompositeJsonMatcher(
newIgnoredPathMatcher("timestamp", "id"), // Must be firstnewLenientJsonArrayPartialMatcher(),
newLenientJsonObjectPartialMatcher(),
newStrictPrimitivePartialMatcher()
);
// These will match with 100% similarity:finalvardiff = DiffGenerator.diff(
"{\"name\": \"John\", \"timestamp\": \"2024-01-01\"}",
"{\"name\": \"John\", \"timestamp\": \"2024-12-31\"}",
jsonMatcher
);
System.out.println(diff.similarityRate()); // 100.0

Path Patterns

The IgnoredPathMatcher supports various path patterns:

PatternDescriptionExample
nameMatches field name at any levelIgnores $.name, $.user.name, $.data.user.name
user.nameMatches name under userIgnores $.user.name, $.data.user.name
*.nameWildcard for any propertyIgnores $.foo.name, $.bar.name
items[0]Matches specific array indexIgnores $.items[0]
items[*]Wildcard for any array indexIgnores $.items[0], $.items[1], etc.
items[*].idField in any array elementIgnores $.items[0].id, $.items[5].id

Examples

// Ignore a single field everywherenewIgnoredPathMatcher("createdAt")
// Ignore multiple fieldsnewIgnoredPathMatcher("createdAt", "updatedAt", "id")
// Ignore nested fieldnewIgnoredPathMatcher("metadata.timestamp")
// Ignore field in all array elementsnewIgnoredPathMatcher("users[*].password")
// Combine multiple patternsnewIgnoredPathMatcher("id", "*.createdAt", "items[*].internalId")

Important:

  • Place IgnoredPathMatcherbefore other matchers in the constructor
  • Patterns match against the end of the path, so name matches $.user.name as well as $.name

Advanced Example

finalvarexpectedJson = """ { "additionalProperty": "a", "foo": "bar", "bar": "bar", "numberMatch": 10.0, "numberUnmatched": 10.01, "arrayMatch": [{"b": "a"}], "arrayUnmatched": [{"b": "a"}] } """;
finalvarreceivedJson = """ { "foo": "foo", "bar": "bar", "numberMatch": 10, "numberUnmatched": 10.02, "arrayMatch": [{"b": "a"}], "arrayUnmatched": {"b": "b"} } """;
finalvarjsonMatcher = newCompositeJsonMatcher(
newLenientJsonArrayPartialMatcher(),
newLenientJsonObjectPartialMatcher(),
newLenientNumberPrimitivePartialMatcher(),
newStrictPrimitivePartialMatcher()
);
finalvardiff = DiffGenerator.diff(expectedJson, receivedJson, jsonMatcher);
System.out.println(OnlyErrorDiffViewer.from(diff));
System.out.println("Similarity: " + diff.similarityRate() + "%");

Output:

The property "$.additionalProperty" is not found
The property "$.numberUnmatched" didn't match. Expected 10.01, Received: 10.02
The property "$.arrayUnmatched" didn't match. Expected [{"b":"a"}], Received: {"b":"b"}
The property "$.foo" didn't match. Expected "bar", Received: "foo"
Similarity: 76.0%

Creating Custom Matchers

You can create custom matchers by implementing the PartialJsonMatcher<T> interface:

publicclassMyCustomMatcherimplementsPartialJsonMatcher<JsonNode> {
@Overridepublicbooleanmanage(JsonNodeexpected, JsonNodereceived) {
// Return true if this matcher should handle this comparisonreturn/* your condition */;
}
@OverridepublicJsonDiffjsonDiff(Pathpath, JsonNodeexpected, JsonNodereceived, JsonMatcherjsonMatcher) {
// Return your diff resultif (/* values match */) {
returnnewMatchedPrimaryDiff(path, expected);
}
returnnewUnMatchedPrimaryDiff(path, expected, received);
}
}

License

This project is licensed under the MIT License.

About

A library to generate a json diff on java

Resources

Stars

58 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages