The method JSONCompareUtil#findUniqueKey is used as static. I am using an JsonComparator derived from DefaultComparator which ignores string-like properties if they match some pattern. But such properties may be elected as unique keys for faster array comparison. The comparison fails in this case even if the property is a one to ignore.
The feature request is to make this logic customizable.
My example with a work-aound is:
package ppm.testsupport;
import io.vavr.collection.HashMap;
import io.vavr.collection.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.JSONCompareResult;
import org.skyscreamer.jsonassert.comparator.DefaultComparator;
import org.skyscreamer.jsonassert.comparator.JSONComparator;
import java.util.regex.Pattern;
import static org.skyscreamer.jsonassert.comparator.JSONCompareUtil.*;
/**
* This class is a lenient JSON comparator that extends the DefaultComparator class.
* It provides additional functionality to ignore certain patterns in JSON values
* during comparison.
*/
public class SuperLenientJsonComparator extends DefaultComparator {
public static JSONComparator LENIENT = new SuperLenientJsonComparator( JSONCompareMode.LENIENT );
private final Map<Pattern, String> patternsToIgnore = HashMap.of(
Pattern.compile( "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{1,9}" ), "LocalDateTime",
Pattern.compile( "ppm:core.cid:\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}" ), "CorrelationId",
Pattern.compile( "ppm:core.eev:\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}:\\d+" ), "EventId",
Pattern.compile( "\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}" ), "UUID",
Pattern.compile( "ppm.core.s:.*" ), "SchedulerId",
Pattern.compile( "PT[.0-9]*S$" ), "Duration"
);
public SuperLenientJsonComparator( JSONCompareMode mode ) {
super( mode );
}
@Override
public void compareValues( String prefix, Object expectedValue, Object actualValue, JSONCompareResult result ) throws JSONException {
boolean ignore = false;
if ( expectedValue instanceof String expected && actualValue instanceof String actual ) {
for ( Pattern pattern : patternsToIgnore.keySet() ) {
if ( pattern.matcher( actual ).matches() && pattern.matcher( expected ).matches() ) {
ignore = true;
break;
}
}
}
if ( !ignore ) {
super.compareValues( prefix, expectedValue, actualValue, result );
}
}
private String findUniqueKeySkipIgnored( JSONArray expected ) throws JSONException {
JSONObject o = (JSONObject) expected.get( 0 ); // There's at least one at this point
searchLoop:
for ( String candidate : getKeys( o ) ) {
var candidateValue = o.get( candidate );
if ( candidateValue instanceof String candidateValueString ) {
for ( var pattern : patternsToIgnore.keySet() ) {
if ( (pattern.matcher( candidateValueString )).matches() ) {
continue searchLoop;
}
}
}
if ( isUsableAsUniqueKey( candidate, expected ) )
return candidate;
}
// No usable unique key :-(
return null;
}
/**
* * code copied from base class, only findUniqueKey is replaced as being static.
*/
@Override
protected void compareJSONArrayOfJsonObjects( String key, JSONArray expected, JSONArray actual, JSONCompareResult result ) throws JSONException {
String uniqueKey = findUniqueKeySkipIgnored( expected );
if ( uniqueKey == null || !isUsableAsUniqueKey( uniqueKey, actual ) ) {
// An expensive last resort
recursivelyCompareJSONArray( key, expected, actual, result );
return;
}
java.util.Map<Object, JSONObject> expectedValueMap = arrayOfJsonObjectToMap( expected, uniqueKey );
java.util.Map<Object, JSONObject> actualValueMap = arrayOfJsonObjectToMap( actual, uniqueKey );
for ( Object id : expectedValueMap.keySet() ) {
if ( !actualValueMap.containsKey( id ) ) {
result.missing( formatUniqueKey( key, uniqueKey, id ), expectedValueMap.get( id ) );
continue;
}
JSONObject expectedValue = expectedValueMap.get( id );
JSONObject actualValue = actualValueMap.get( id );
compareValues( formatUniqueKey( key, uniqueKey, id ), expectedValue, actualValue, result );
}
for ( Object id : actualValueMap.keySet() ) {
if ( !expectedValueMap.containsKey( id ) ) {
result.unexpected( formatUniqueKey( key, uniqueKey, id ), actualValueMap.get( id ) );
}
}
}
@Override
public void compareJSONArray( String prefix, JSONArray expected, JSONArray actual, JSONCompareResult result ) throws JSONException {
replaceIgnorableStringValuesWithFixedPattern( expected );
replaceIgnorableStringValuesWithFixedPattern( actual );
super.compareJSONArray( prefix, expected, actual, result );
}
private void replaceIgnorableStringValuesWithFixedPattern( JSONArray array ) throws JSONException {
for ( int i = 0; i < array.length(); i++ ) {
if ( !array.isNull( i ) ) {
var element = array.get( i );
if ( element instanceof String stringValue ) {
for ( var pattern : patternsToIgnore ) {
if ( pattern._1().matcher( stringValue ).matches() ) {
array.put( i, pattern._2() );
break;
}
}
}
}
}
}
}
The method JSONCompareUtil#findUniqueKey is used as static. I am using an JsonComparator derived from DefaultComparator which ignores string-like properties if they match some pattern. But such properties may be elected as unique keys for faster array comparison. The comparison fails in this case even if the property is a one to ignore.
The feature request is to make this logic customizable.
My example with a work-aound is: