Class DeepEquals

java.lang.Object
com.cedarsoftware.util.DeepEquals

public class DeepEquals extends Object
Performs a deep comparison of two objects, going beyond simple equals() checks. Handles nested objects, collections, arrays, and maps while detecting circular references.

Key features:

  • Compares entire object graphs including nested structures
  • Handles circular references safely
  • Provides detailed difference descriptions for troubleshooting
  • Supports numeric comparisons with configurable precision
  • Supports selective ignoring of custom equals() implementations
  • Supports string-to-number equality comparisons

Options:

  • IGNORE_CUSTOM_EQUALS (a Set<Class<?>>):
    • null — Use all custom equals() methods (ignore none).
    • Empty set — Ignore all custom equals() methods.
    • Non-empty set — Ignore only those classes’ custom equals() implementations.
  • ALLOW_STRINGS_TO_MATCH_NUMBERS (a Boolean): If set to true, allows strings like "10" to match the numeric value 10.

The options Map acts as both input and output. When objects differ, the difference description is placed in the options Map under the "diff" key (see deepEquals).

"diff" output notes:

  • Empty lists, maps, and arrays are shown with (∅) or [∅]
  • A Map of size 1 is shown as Map(0..0), an int[] of size 2 is shown as int[0..1], an empty list is List(∅)
  • Sub-object fields on non-difference path shown as {..}
  • Map entry shown with 《key ⇨ value》 and may be nested
  • General pattern is [difference type] ▶ root context ▶ shorthand path starting at a root context element (Object field, array/collection element, Map key-value)
  • If the root is not a container (Collection, Map, Array, or Object), no shorthand description is displayed

Example usage:


 Map<String, Object> options = new HashMap<>();
 options.put(IGNORE_CUSTOM_EQUALS, Set.of(MyClass.class, OtherClass.class));
 options.put(ALLOW_STRINGS_TO_MATCH_NUMBERS, true);

 if (!DeepEquals.deepEquals(obj1, obj2, options)) {
     String diff = (String) options.get(DeepEquals.DIFF);  // Get difference description
     // Handle or log 'diff'

 Example output:
 // Simple object difference
 [field value mismatch] ▶ Person {name: "Jim Bob", age: 27} ▶ .age
   Expected: 27
   Found: 34
   
 // Array element mismatch within an object that has an array
 [array element mismatch] ▶ Person {id: 173679590720000287, first: "John", last: "Smith", favoritePet: {..}, pets: Pet[0..1]} ▶ .pets[0].nickNames[0]
   Expected: "Edward"
   Found: "Eddie"

 // Map with a different value associated to a key (Map size = 1 noted as 0..0)
 [map value mismatch] ▶ LinkedHashMap(0..0) ▶ 《"key" ⇨ "value1"》
   Expected: "value1"
   Found: "value2"
   
 
Author:
John DeRegnaucourt ([email protected])
Copyright (c) Cedar Software LLC

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

License

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
See Also:
  • Field Details

  • Constructor Details

    • DeepEquals

      public DeepEquals()
  • Method Details

    • deepEquals

      public static boolean deepEquals(Object a, Object b)
      Performs a deep comparison between two objects, going beyond a simple equals() check.

      This method is functionally equivalent to calling deepEquals(a, b, new HashMap<>()), which means it uses no additional comparison options. In other words:

      • IGNORE_CUSTOM_EQUALS is not set (all custom equals() methods are used)
      • ALLOW_STRINGS_TO_MATCH_NUMBERS defaults to false

      Parameters:
      a - the first object to compare, may be null
      b - the second object to compare, may be null
      Returns:
      true if the two objects are deeply equal, false otherwise
      See Also:
    • deepEquals

      public static boolean deepEquals(Object a, Object b, Map<String,?> options)
      Performs a deep comparison between two objects with optional comparison settings.

      In addition to comparing objects, collections, maps, and arrays for equality of nested elements, this method can also:

      • Ignore certain classes' custom equals() methods according to user-defined rules
      • Allow string-to-number comparisons (e.g., "10" equals 10)
      • Handle floating-point comparisons with tolerance for precision
      • Detect and handle circular references to avoid infinite loops

      Options:

      • DeepEquals.IGNORE_CUSTOM_EQUALS (a Collection<Class<?>>):
        • null — Use all custom equals() methods (ignore none). Default.
        • Empty set — Ignore all custom equals() methods.
        • Non-empty set — Ignore only those classes’ custom equals() implementations.
      • DeepEquals.ALLOW_STRINGS_TO_MATCH_NUMBERS (a Boolean): If set to true, allows strings like "10" to match the numeric value 10. Default false.

      If the objects differ, a difference description string is stored in options under the key "diff". The key "diff_item" can provide additional context regarding the specific location of the mismatch.

      Parameters:
      a - the first object to compare, may be null
      b - the second object to compare, may be null
      options - a map of comparison options and, on return, possibly difference details
      Returns:
      true if the two objects are deeply equal, false otherwise
      See Also:
    • hasCustomEquals

      public static boolean hasCustomEquals(Class<?> c)
      Determines whether the given class has a custom equals(Object) method distinct from Object.equals(Object).

      Useful for detecting when a class relies on a specialized equality definition, which can be selectively ignored by deep-comparison if desired.

      Parameters:
      c - the class to inspect, must not be null
      Returns:
      true if c declares its own equals(Object) method, false otherwise
    • hasCustomHashCode

      public static boolean hasCustomHashCode(Class<?> c)
      Determines whether the given class has a custom hashCode() method distinct from Object.hashCode().

      This method helps identify classes that rely on a specialized hashing algorithm, which can be relevant for certain comparison or hashing scenarios.

      Usage Example:

      
       Class<?> clazz = MyCustomClass.class;
       boolean hasCustomHashCode = hasCustomHashCodeMethod(clazz);
       System.out.println("Has custom hashCode(): " + hasCustomHashCode);
       

      Notes:

      • A class is considered to have a custom hashCode() method if it declares its own hashCode() method that is not inherited directly from Object.
      • This method does not consider interfaces or abstract classes unless they declare a hashCode() method.
      Parameters:
      c - the class to inspect, must not be null
      Returns:
      true if c declares its own hashCode() method, false otherwise
      Throws:
      IllegalArgumentException - if the provided class c is null
      See Also:
    • deepHashCode

      public static int deepHashCode(Object obj)
      Computes a deep hash code for the given object by traversing its entire graph.

      This method considers the hash codes of nested objects, arrays, maps, and collections, and uses cyclic reference detection to avoid infinite loops.

      While deepHashCode() enables O(n) comparison performance in DeepEquals() when comparing unordered collections and maps, it does not guarantee that objects which are deepEquals() will have matching deepHashCode() values. This design choice allows for optimized performance while maintaining correctness of equality comparisons.

      You can use it for generating your own hashCodes() on complex items, but understand that it *always* calls an instants hashCode() method if it has one that override's the hashCode() method defined on Object.class.

      Parameters:
      obj - the object to hash, may be null
      Returns:
      an integer representing the object's deep hash code