Skip to content

Commit f16e618

Browse files
deblocktclaude
andcommitted
docs: update README for 2.0.0
- Improve English and structure - Update installation to version 2.0.0 with Java 21/Jackson 3.x note - Add Quick Start section - Document all available matchers in tables - Add NullEqualsEmptyArrayMatcher section - Add Creating Custom Matchers section - Update examples for new CompositeJsonMatcher API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 50dc7c5 commit f16e618

2 files changed

Lines changed: 212 additions & 84 deletions

File tree

README.md

Lines changed: 212 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,264 @@
11
# Java json-diff
22

3-
A customizable lib to perform a json-diff
3+
A customizable library to perform JSON comparisons with detailed diff output.
44

5-
## Why Use json-diff library
5+
## Why Use json-diff?
66

7-
The goal of this library is to provide a readable diff between two json file.
7+
This library provides:
88

9-
In addition to the differential, a similarity score is calculated.
10-
This score can be used to compare several json with each other and find the two most similar.
11-
12-
The way to compare json is completely customisable.
13-
14-
2 way to display diff are provided by default (patch file, text file). And you can easily create your own formatter.
9+
- **Readable diffs** between two JSON documents
10+
- **Similarity scoring** (0-100) to compare multiple JSON documents and find the most similar ones
11+
- **Fully customizable** comparison modes (strict, lenient, or mixed)
12+
- **Multiple output formats** (patch file, text) with the ability to create custom formatters
1513

1614
## Installation
1715

18-
maven:
16+
**Maven:**
1917
```xml
2018
<dependency>
2119
<groupId>io.github.deblockt</groupId>
2220
<artifactId>json-diff</artifactId>
23-
<version>1.1.0</version>
21+
<version>2.0.0</version>
2422
</dependency>
2523
```
2624

27-
gradle:
25+
**Gradle:**
2826
```gradle
29-
implementation 'io.github.deblockt:json-diff:1.1.0'
27+
implementation 'io.github.deblockt:json-diff:2.0.0'
3028
```
3129

32-
## Usage
30+
> **Note:** Version 2.0.0 requires Java 21+ and uses Jackson 3.x
31+
32+
## Quick Start
3333

34-
example:
3534
```java
36-
final var expectedJson = "{\"additionalProperty\":\"a\", \"foo\": \"bar\", \"bar\": \"bar\", \"numberMatch\": 10.0, \"numberUnmatched\": 10.01, \"arrayMatch\": [{\"b\":\"a\"}], \"arrayUnmatched\": [{\"b\":\"a\"}]}";
37-
final var receivedJson = "{\"foo\": \"foo\", \"bar\": \"bar\", \"numberMatch\": 10, \"numberUnmatched\": 10.02, \"arrayMatch\": [{\"b\":\"a\"}], \"arrayUnmatched\": {\"b\":\"b\"}}";
35+
final var expectedJson = "{\"name\": \"John\", \"age\": 30, \"city\": \"Paris\"}";
36+
final var receivedJson = "{\"name\": \"Jane\", \"age\": 30, \"country\": \"France\"}";
3837

39-
// define your matcher
40-
// CompositeJsonMatcher use other matcher to perform matching on objects, list or primitive
38+
// Define your matcher
4139
final var jsonMatcher = new CompositeJsonMatcher(
42-
new LenientJsonArrayPartialMatcher(), // comparing array using lenient mode (ignore array order and extra items)
43-
new LenientJsonObjectPartialMatcher(), // comparing object using lenient mode (ignoring extra properties)
44-
new LenientNumberPrimitivePartialMatcher(new StrictPrimitivePartialMatcher()) // comparing primitive types and manage numbers (100.00 == 100)
40+
new LenientJsonArrayPartialMatcher(),
41+
new LenientJsonObjectPartialMatcher(),
42+
new StrictPrimitivePartialMatcher()
4543
);
4644

47-
// generate a diff
48-
final var jsondiff = DiffGenerator.diff(expectedJson, receivedJson, jsonMatcher);
45+
// Generate the diff
46+
final var diff = DiffGenerator.diff(expectedJson, receivedJson, jsonMatcher);
4947

50-
// use the viewer to collect diff data
51-
final var errorsResult= OnlyErrorDiffViewer.from(jsondiff);
48+
// Display errors
49+
System.out.println(OnlyErrorDiffViewer.from(diff));
5250

53-
// print the diff result
54-
System.out.println(errorsResult);
55-
// print a similarity ratio between expected and received json (0 <= ratio <= 100)
56-
System.out.println(jsondiff.similarityRate());
51+
// Get similarity score (0-100)
52+
System.out.println("Similarity: " + diff.similarityRate() + "%");
5753
```
58-
Result:
59-
```
60-
The property "$.additionalProperty" is not found
61-
The property "$.numberUnmatched" didn't match. Expected 10.01, Received: 10.02
62-
The property "$.arrayUnmatched" didn't match. Expected [{"b":"a"}], Received: {"b":"b"}
63-
The property "$.foo" didn't match. Expected "bar", Received: "foo"
6454

65-
76.0
66-
```
55+
## Output Formats
56+
57+
### Error List (OnlyErrorDiffViewer)
6758

68-
You can also generate a patch file using this viewer:
6959
```java
70-
final var patch = PatchDiffViewer.from(jsondiff);
60+
final var errors = OnlyErrorDiffViewer.from(diff);
61+
System.out.println(errors);
62+
```
63+
64+
Output:
65+
```
66+
The property "$.city" is not found
67+
The property "$.name" didn't match. Expected "John", Received: "Jane"
68+
```
7169

72-
// use the viewer to collect diff data
73-
final var patchFile= PatchDiffViewer.from(jsondiff);
70+
### Patch Format (PatchDiffViewer)
7471

75-
// print the diff result
76-
System.out.println(patchFile);
72+
```java
73+
final var patch = PatchDiffViewer.from(diff);
74+
System.out.println(patch);
7775
```
7876

79-
Result:
80-
``` diff
77+
Output:
78+
```diff
8179
--- actual
8280
+++ expected
8381
@@ @@
8482
{
85-
+ "additionalProperty": "a",
86-
"bar": "bar",
87-
- "numberUnmatched": 10.02,
88-
+ "numberUnmatched": 10.01,
89-
- "arrayUnmatched": {"b":"b"},
90-
+ "arrayUnmatched": [{"b":"a"}],
91-
- "foo": "foo",
92-
+ "foo": "bar",
93-
"numberMatch": 10.0,
94-
"arrayMatch": [
95-
{
96-
"b": "a"
97-
}
98-
]
83+
"age": 30,
84+
+ "city": "Paris",
85+
- "country": "France",
86+
- "name": "Jane",
87+
+ "name": "John"
9988
}
10089
```
10190

102-
### Comparison mode
91+
## Comparison Modes
92+
93+
`CompositeJsonMatcher` accepts multiple matchers that handle different JSON types. The order matters: the first matcher that can handle a comparison will be used.
10394

104-
You can use many comparison mode to compare you json:
95+
### Lenient Mode
10596

106-
If you want compare json using *lenient* comparison:
107-
```java
108-
final var fullLenient = new CompositeJsonMatcher(
109-
new LenientJsonArrayPartialMatcher(), // comparing array using lenient mode (ignore array order and extra items)
110-
new LenientJsonObjectPartialMatcher(), // comparing object using lenient mode (ignoring extra properties)
111-
new LenientNumberPrimitivePartialMatcher(new StrictPrimitivePartialMatcher()) // comparing primitive types and manage numbers (100.00 == 100)
97+
Ignores extra properties and array order:
98+
99+
```java
100+
final var lenientMatcher = new CompositeJsonMatcher(
101+
new LenientJsonArrayPartialMatcher(), // Ignores array order and extra items
102+
new LenientJsonObjectPartialMatcher(), // Ignores extra properties
103+
new LenientNumberPrimitivePartialMatcher(new StrictPrimitivePartialMatcher()) // 10.0 == 10
112104
);
113105
```
114106

115-
If you want compare json using *strict* comparison:
116-
```java
107+
### Strict Mode
108+
109+
Requires exact matches:
110+
111+
```java
117112
final var strictMatcher = new CompositeJsonMatcher(
118-
new StrictJsonArrayPartialMatcher(), // comparing array using strict mode (object should have same properties/value)
119-
new StrictJsonObjectPartialMatcher(), // comparing object using strict mode (array should have same item on same orders)
120-
new StrictPrimitivePartialMatcher() // comparing primitive types (values should be strictly equals type and value)
113+
new StrictJsonArrayPartialMatcher(), // Same items in same order
114+
new StrictJsonObjectPartialMatcher(), // Same properties, no extras
115+
new StrictPrimitivePartialMatcher() // Exact type and value match
116+
);
117+
```
118+
119+
### Mixed Mode
120+
121+
You can combine matchers for custom behavior:
122+
123+
```java
124+
final var mixedMatcher = new CompositeJsonMatcher(
125+
new LenientJsonArrayPartialMatcher(), // Lenient on arrays
126+
new StrictJsonObjectPartialMatcher(), // Strict on objects
127+
new StrictPrimitivePartialMatcher()
128+
);
129+
```
130+
131+
## Available Matchers
132+
133+
### Array Matchers
134+
135+
| Matcher | Description |
136+
|---------|-------------|
137+
| `LenientJsonArrayPartialMatcher` | Ignores array order and extra items |
138+
| `StrictJsonArrayPartialMatcher` | Requires same items in same order |
139+
140+
### Object Matchers
141+
142+
| Matcher | Description |
143+
|---------|-------------|
144+
| `LenientJsonObjectPartialMatcher` | Ignores extra properties in received JSON |
145+
| `StrictJsonObjectPartialMatcher` | Requires exact same properties |
146+
147+
### Primitive Matchers
148+
149+
| Matcher | Description |
150+
|---------|-------------|
151+
| `StrictPrimitivePartialMatcher` | Exact type and value match |
152+
| `LenientNumberPrimitivePartialMatcher` | Numbers are equal if values match (`10.0 == 10`) |
153+
154+
### Special Matchers
155+
156+
| Matcher | Description |
157+
|---------|-------------|
158+
| `NullEqualsEmptyArrayMatcher` | Treats `null` and `[]` as equivalent |
159+
160+
## Treating Null as Empty Array
161+
162+
The `NullEqualsEmptyArrayMatcher` allows you to consider `null` values and empty arrays `[]` as equivalent. This is useful when different systems represent "no data" differently.
163+
164+
```java
165+
final var jsonMatcher = new CompositeJsonMatcher(
166+
new NullEqualsEmptyArrayMatcher(), // Must be first to handle null vs []
167+
new LenientJsonArrayPartialMatcher(),
168+
new LenientJsonObjectPartialMatcher(),
169+
new StrictPrimitivePartialMatcher()
170+
);
171+
172+
// These will match with 100% similarity:
173+
// {"items": null} vs {"items": []}
174+
// {"items": []} vs {"items": null}
175+
176+
final var diff = DiffGenerator.diff(
177+
"{\"items\": null}",
178+
"{\"items\": []}",
179+
jsonMatcher
121180
);
181+
182+
System.out.println(diff.similarityRate()); // 100.0
122183
```
123184

124-
You can mix matcher. For example, be lenient on array and strict on object:
125-
```java
126-
final var matcher = new CompositeJsonMatcher(
127-
new LenientJsonArrayPartialMatcher(), // comparing array using lenient mode (ignore array order and extra items)
128-
new StrictJsonObjectPartialMatcher(), // comparing object using strict mode (array should have same item on same orders)
129-
new StrictPrimitivePartialMatcher() // comparing primitive types (values should be strictly equals type and value)
185+
**Important:**
186+
- Place `NullEqualsEmptyArrayMatcher` **before** other matchers in the constructor
187+
- This matcher only handles `null` vs empty array `[]`, not missing properties
188+
- Non-empty arrays do not match `null`
189+
190+
## Advanced Example
191+
192+
```java
193+
final var expectedJson = """
194+
{
195+
"additionalProperty": "a",
196+
"foo": "bar",
197+
"bar": "bar",
198+
"numberMatch": 10.0,
199+
"numberUnmatched": 10.01,
200+
"arrayMatch": [{"b": "a"}],
201+
"arrayUnmatched": [{"b": "a"}]
202+
}
203+
""";
204+
205+
final var receivedJson = """
206+
{
207+
"foo": "foo",
208+
"bar": "bar",
209+
"numberMatch": 10,
210+
"numberUnmatched": 10.02,
211+
"arrayMatch": [{"b": "a"}],
212+
"arrayUnmatched": {"b": "b"}
213+
}
214+
""";
215+
216+
final var jsonMatcher = new CompositeJsonMatcher(
217+
new LenientJsonArrayPartialMatcher(),
218+
new LenientJsonObjectPartialMatcher(),
219+
new LenientNumberPrimitivePartialMatcher(new StrictPrimitivePartialMatcher())
130220
);
221+
222+
final var diff = DiffGenerator.diff(expectedJson, receivedJson, jsonMatcher);
223+
224+
System.out.println(OnlyErrorDiffViewer.from(diff));
225+
System.out.println("Similarity: " + diff.similarityRate() + "%");
226+
```
227+
228+
Output:
229+
```
230+
The property "$.additionalProperty" is not found
231+
The property "$.numberUnmatched" didn't match. Expected 10.01, Received: 10.02
232+
The property "$.arrayUnmatched" didn't match. Expected [{"b":"a"}], Received: {"b":"b"}
233+
The property "$.foo" didn't match. Expected "bar", Received: "foo"
234+
235+
Similarity: 76.0%
131236
```
237+
238+
## Creating Custom Matchers
239+
240+
You can create custom matchers by implementing the `PartialJsonMatcher<T>` interface:
241+
242+
```java
243+
public class MyCustomMatcher implements PartialJsonMatcher<JsonNode> {
244+
245+
@Override
246+
public boolean manage(JsonNode expected, JsonNode received) {
247+
// Return true if this matcher should handle this comparison
248+
return /* your condition */;
249+
}
250+
251+
@Override
252+
public JsonDiff jsonDiff(Path path, JsonNode expected, JsonNode received, JsonMatcher jsonMatcher) {
253+
// Return your diff result
254+
if (/* values match */) {
255+
return new MatchedPrimaryDiff(path, expected);
256+
}
257+
return new UnMatchedPrimaryDiff(path, expected, received);
258+
}
259+
}
260+
```
261+
262+
## License
263+
264+
This project is licensed under the MIT License.

src/test/java/com/deblock/jsondiff/integration/NullEqualsEmptyArrayMatcherIntegrationTest.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,6 @@ public void shouldMatchMultipleNullAndEmptyArrayFields() {
8787

8888
@Test
8989
public void shouldNotMatchEmptyArrayAndMissingProperty() {
90-
// Note: This case is intentionally NOT supported by NullEqualsEmptyArrayMatcher
91-
// Missing property is different from null or empty array
9290
final var expected = "{\"items\": []}";
9391
final var received = "{}";
9492

@@ -99,14 +97,11 @@ public void shouldNotMatchEmptyArrayAndMissingProperty() {
9997

10098
@Test
10199
public void shouldNotMatchMissingPropertyAndEmptyArray() {
102-
// Note: This case is intentionally NOT supported by NullEqualsEmptyArrayMatcher
103100
final var expected = "{}";
104101
final var received = "{\"items\": []}";
105102

106103
final var diff = DiffGenerator.diff(expected, received, jsonMatcher);
107104

108-
// With LenientJsonObjectPartialMatcher, extra properties are ignored
109-
// So this should match 100% (expected has no requirements)
110105
assertEquals(100.0, diff.similarityRate());
111106
}
112107
}

0 commit comments

Comments
 (0)