0
# JSON Comparators
1
2
Pluggable comparison strategies including default comparison logic, custom field-level matching, and specialized array size comparison for different validation scenarios. The comparator system provides the core comparison engine that can be extended and customized for specific testing needs.
3
4
## Capabilities
5
6
### JSONComparator Interface
7
8
Core interface defining the contract for JSON comparison implementations.
9
10
```java { .api }
11
public interface JSONComparator {
12
/**
13
* Compare two JSONObjects and return detailed results.
14
*/
15
JSONCompareResult compareJSON(JSONObject expected, JSONObject actual) throws JSONException;
16
17
/**
18
* Compare two JSONArrays and return detailed results.
19
*/
20
JSONCompareResult compareJSON(JSONArray expected, JSONArray actual) throws JSONException;
21
22
/**
23
* Compare JSONObjects with path context, updating result in-place.
24
*/
25
void compareJSON(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException;
26
27
/**
28
* Compare individual values with path context, updating result in-place.
29
*/
30
void compareValues(String prefix, Object expectedValue, Object actualValue, JSONCompareResult result) throws JSONException;
31
32
/**
33
* Compare JSONArrays with path context, updating result in-place.
34
*/
35
void compareJSONArray(String prefix, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException;
36
}
37
```
38
39
### Default Comparator
40
41
Standard implementation providing configurable comparison behavior based on JSONCompareMode settings.
42
43
```java { .api }
44
public class DefaultComparator implements JSONComparator {
45
/**
46
* Create comparator with specified comparison mode.
47
*/
48
public DefaultComparator(JSONCompareMode mode);
49
50
public JSONCompareResult compareJSON(JSONObject expected, JSONObject actual) throws JSONException;
51
public JSONCompareResult compareJSON(JSONArray expected, JSONArray actual) throws JSONException;
52
public void compareJSON(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException;
53
public void compareValues(String prefix, Object expectedValue, Object actualValue, JSONCompareResult result) throws JSONException;
54
public void compareJSONArray(String prefix, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException;
55
}
56
```
57
58
**Usage Examples:**
59
60
```java
61
// Create comparators with different modes
62
DefaultComparator strictComparator = new DefaultComparator(JSONCompareMode.STRICT);
63
DefaultComparator lenientComparator = new DefaultComparator(JSONCompareMode.LENIENT);
64
65
// Use directly for programmatic comparison
66
JSONCompareResult result = strictComparator.compareJSON(expectedObject, actualObject);
67
68
// Use with JSONAssert
69
JSONAssert.assertEquals(expected, actual, lenientComparator);
70
```
71
72
### Custom Comparator
73
74
Extends DefaultComparator to support field-specific custom matching through Customization objects.
75
76
```java { .api }
77
public class CustomComparator extends DefaultComparator {
78
/**
79
* Create custom comparator with base mode and field customizations.
80
*
81
* @param mode base comparison mode for non-customized fields
82
* @param customizations variable number of field-specific customizations
83
*/
84
public CustomComparator(JSONCompareMode mode, Customization... customizations);
85
86
@Override
87
public void compareValues(String prefix, Object expectedValue, Object actualValue, JSONCompareResult result) throws JSONException;
88
}
89
```
90
91
**Usage Examples:**
92
93
```java
94
// Single customization
95
RegularExpressionValueMatcher<Object> emailMatcher =
96
new RegularExpressionValueMatcher<>("^[\\w._%+-]+@[\\w.-]+\\.[A-Z]{2,}$");
97
Customization emailCustomization = new Customization("user.email", emailMatcher);
98
CustomComparator emailComparator = new CustomComparator(JSONCompareMode.LENIENT, emailCustomization);
99
100
// Multiple customizations
101
Customization timestampCustomization = new Customization("**.timestamp",
102
new RegularExpressionValueMatcher<>("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"));
103
104
Customization idCustomization = new Customization("**.id",
105
new RegularExpressionValueMatcher<>("^\\d+$"));
106
107
CustomComparator multiCustomComparator = new CustomComparator(
108
JSONCompareMode.LENIENT,
109
emailCustomization,
110
timestampCustomization,
111
idCustomization
112
);
113
114
// Use with assertions
115
JSONAssert.assertEquals(expected, actual, multiCustomComparator);
116
```
117
118
### Abstract Comparator
119
120
Base class providing common functionality for comparator implementations.
121
122
```java { .api }
123
public abstract class AbstractComparator implements JSONComparator {
124
/**
125
* Protected constructor for subclasses.
126
*/
127
protected AbstractComparator();
128
129
// Provides default implementations of interface methods
130
// that can be overridden by subclasses
131
}
132
```
133
134
### Array Size Comparator
135
136
Specialized comparator that compares arrays by size rather than content, useful for validating array length constraints.
137
138
```java { .api }
139
public class ArraySizeComparator extends DefaultComparator {
140
/**
141
* Create array size comparator with specified mode.
142
*/
143
public ArraySizeComparator(JSONCompareMode mode);
144
145
// Compares arrays by length instead of element-by-element comparison
146
}
147
```
148
149
**Usage Examples:**
150
151
```java
152
// Validate that arrays have expected lengths
153
ArraySizeComparator sizeComparator = new ArraySizeComparator(JSONCompareMode.STRICT_ORDER);
154
155
// Expected array length is encoded in the expected JSON
156
String expected = "{\"items\":[1,2,3]}"; // Expects 3 elements
157
String actual = "{\"items\":[\"a\",\"b\",\"c\"]}"; // Has 3 elements
158
159
JSONAssert.assertEquals(expected, actual, sizeComparator); // Passes - same length
160
161
// Use with array value matcher for partial size validation
162
ArrayValueMatcher<Object> sizeArrayMatcher = new ArrayValueMatcher<>(sizeComparator, 0, 2);
163
Customization sizeCustomization = new Customization("data.arrays.*", sizeArrayMatcher);
164
```
165
166
### JSONCompare Utility
167
168
Utility class providing convenience methods for JSON comparison operations.
169
170
```java { .api }
171
public class JSONCompareUtil {
172
// Static utility methods for common comparison operations
173
// (Implementation details vary - check source for specific methods)
174
}
175
```
176
177
## Advanced Comparator Patterns
178
179
### Custom Comparator Implementation
180
181
```java
182
// Example: Case-insensitive string comparator
183
public class CaseInsensitiveComparator extends DefaultComparator {
184
public CaseInsensitiveComparator(JSONCompareMode mode) {
185
super(mode);
186
}
187
188
@Override
189
public void compareValues(String prefix, Object expectedValue, Object actualValue, JSONCompareResult result) throws JSONException {
190
if (expectedValue instanceof String && actualValue instanceof String) {
191
String expectedStr = (String) expectedValue;
192
String actualStr = (String) actualValue;
193
194
if (!expectedStr.equalsIgnoreCase(actualStr)) {
195
result.fail(prefix, expectedValue, actualValue);
196
}
197
} else {
198
super.compareValues(prefix, expectedValue, actualValue, result);
199
}
200
}
201
}
202
203
// Usage
204
CaseInsensitiveComparator caseInsensitive = new CaseInsensitiveComparator(JSONCompareMode.LENIENT);
205
JSONAssert.assertEquals("{\"name\":\"john\"}", "{\"name\":\"JOHN\"}", caseInsensitive);
206
```
207
208
### Nested Custom Comparators
209
210
```java
211
// Complex validation with nested customizations
212
public void validateComplexJson() throws JSONException {
213
// Inner comparator for validating array elements
214
RegularExpressionValueMatcher<Object> idMatcher = new RegularExpressionValueMatcher<>("^ID-\\d+$");
215
Customization innerIdCustomization = new Customization("id", idMatcher);
216
CustomComparator innerComparator = new CustomComparator(JSONCompareMode.LENIENT, innerIdCustomization);
217
218
// Array matcher using the inner comparator
219
ArrayValueMatcher<Object> arrayMatcher = new ArrayValueMatcher<>(innerComparator);
220
Customization arrayCustomization = new Customization("users", arrayMatcher);
221
222
// Outer comparator with email validation and array validation
223
RegularExpressionValueMatcher<Object> emailMatcher = new RegularExpressionValueMatcher<>("^[\\w._%+-]+@[\\w.-]+\\.[A-Z]{2,}$");
224
Customization emailCustomization = new Customization("contact.email", emailMatcher);
225
226
CustomComparator outerComparator = new CustomComparator(
227
JSONCompareMode.LENIENT,
228
arrayCustomization,
229
emailCustomization
230
);
231
232
String expected = """
233
{
234
"contact": {"email": "user@example.com"},
235
"users": [{"id": "ID-PLACEHOLDER", "name": "test"}]
236
}""";
237
238
String actual = """
239
{
240
"contact": {"email": "user@example.com"},
241
"users": [
242
{"id": "ID-123", "name": "test", "active": true},
243
{"id": "ID-456", "name": "test", "role": "admin"}
244
]
245
}""";
246
247
JSONAssert.assertEquals(expected, actual, outerComparator);
248
}
249
```
250
251
### Combining Different Comparison Strategies
252
253
```java
254
// Multi-strategy validation approach
255
public class HybridComparator extends DefaultComparator {
256
private final ArraySizeComparator sizeComparator;
257
private final CustomComparator customComparator;
258
259
public HybridComparator(JSONCompareMode mode, Customization... customizations) {
260
super(mode);
261
this.sizeComparator = new ArraySizeComparator(mode);
262
this.customComparator = new CustomComparator(mode, customizations);
263
}
264
265
@Override
266
public void compareJSONArray(String prefix, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException {
267
// First check array sizes
268
if (prefix.endsWith(".sizes")) {
269
sizeComparator.compareJSONArray(prefix, expected, actual, result);
270
} else if (hasCustomization(prefix)) {
271
customComparator.compareJSONArray(prefix, expected, actual, result);
272
} else {
273
super.compareJSONArray(prefix, expected, actual, result);
274
}
275
}
276
277
private boolean hasCustomization(String prefix) {
278
// Logic to determine if path has customizations
279
return customComparator.hasCustomizationForPath(prefix);
280
}
281
}
282
```
283
284
### Error Handling in Custom Comparators
285
286
```java
287
public class ValidatingComparator extends DefaultComparator {
288
public ValidatingComparator(JSONCompareMode mode) {
289
super(mode);
290
}
291
292
@Override
293
public void compareValues(String prefix, Object expectedValue, Object actualValue, JSONCompareResult result) throws JSONException {
294
try {
295
// Custom validation logic
296
if (prefix.contains("date")) {
297
validateDateFormat(expectedValue, actualValue, result, prefix);
298
} else if (prefix.contains("url")) {
299
validateUrlFormat(expectedValue, actualValue, result, prefix);
300
} else {
301
super.compareValues(prefix, expectedValue, actualValue, result);
302
}
303
} catch (Exception e) {
304
result.fail(prefix + " validation error: " + e.getMessage());
305
}
306
}
307
308
private void validateDateFormat(Object expected, Object actual, JSONCompareResult result, String prefix) {
309
// Custom date validation logic
310
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
311
try {
312
dateFormat.parse(actual.toString());
313
// Date is valid, consider it a match regardless of expected value
314
} catch (ParseException e) {
315
result.fail(prefix, "valid date format", actual);
316
}
317
}
318
319
private void validateUrlFormat(Object expected, Object actual, JSONCompareResult result, String prefix) {
320
try {
321
new URL(actual.toString());
322
// URL is valid
323
} catch (MalformedURLException e) {
324
result.fail(prefix, "valid URL format", actual);
325
}
326
}
327
}
328
```
329
330
## Integration Points
331
332
### With JSONAssert
333
334
```java
335
// All assertion methods accept JSONComparator
336
JSONAssert.assertEquals(expected, actual, customComparator);
337
JSONAssert.assertNotEquals(expected, actual, customComparator);
338
```
339
340
### With JSONCompare
341
342
```java
343
// Direct use with comparison methods
344
JSONCompareResult result = JSONCompare.compareJSON(expected, actual, customComparator);
345
```
346
347
### Factory Pattern
348
349
```java
350
public class ComparatorFactory {
351
public static JSONComparator createEmailValidatingComparator() {
352
RegularExpressionValueMatcher<Object> emailMatcher =
353
new RegularExpressionValueMatcher<>("^[\\w._%+-]+@[\\w.-]+\\.[A-Z]{2,}$");
354
Customization emailCustomization = new Customization("**.email", emailMatcher);
355
return new CustomComparator(JSONCompareMode.LENIENT, emailCustomization);
356
}
357
358
public static JSONComparator createStrictArrayComparator() {
359
return new DefaultComparator(JSONCompareMode.STRICT_ORDER);
360
}
361
362
public static JSONComparator createSizeOnlyComparator() {
363
return new ArraySizeComparator(JSONCompareMode.LENIENT);
364
}
365
}
366
```