Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make method findUniqueKey in AbstractComparator customizable #180

Open
astmuc opened this issue Jan 22, 2024 · 1 comment
Open

make method findUniqueKey in AbstractComparator customizable #180

astmuc opened this issue Jan 22, 2024 · 1 comment

Comments

@astmuc
Copy link

astmuc commented Jan 22, 2024

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;
            }
          }
        }
      }
    }
  }
}
@quxiaojing
Copy link

I'am also find a way to manually set uniqueKey , like id . it always select the un-wanted uniqueKey

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants