12

Is there some function in scala that will compare two objects structurally at runtime? I'd rather not have to manually create an equals function due to the number of fields. This is for a unit test, so performance isn't an issue. I just need something that reflectively looks through the fields and compares the values.

Background: I have a case class that extends from a trait that overrides equals and thus doesn't generate the structural equals (squeryl KeyedEntity, related: Prevent Mixin overriding equals from breaking case class equality), and I'm not in a position to change that right now. This is for a unit test so both instances are not persisted.

Community
  • 1
  • 1
ttt
  • 219
  • 1
  • 9

2 Answers2

1

AFAIK the Scala standard library does not contain such a function. But you have two options.

  1. You write a macro, which generates a method to compare equality of two given values
  2. Use Java reflection if performance does not matter. See this answer: Is there a Java reflection utility to do a deep comparison of two objects?
Community
  • 1
  • 1
Joa Ebert
  • 6,565
  • 7
  • 33
  • 47
1

I came up with this solution which iterates through all fields, and for private fields, finds a corresponding method in scala. Indeed, case classes have private members accessible through methods of the same name.

implicit class ReallyEqual[T](a: T) {
  import java.lang.reflect.Modifier._
  def ==:==[U](b: U): Boolean = {
    val fieldsA = a.getClass.getDeclaredFields
    val methodsA = a.getClass.getDeclaredMethods
    val mapA = (methodsA.toList map (z => (z.getName(), z))).toMap
    val fieldsB = b.getClass.getDeclaredFields
    val methodsB = b.getClass.getDeclaredMethods
    val mapB = (methodsB.toList map (z => (z.getName(), z))).toMap
    fieldsA.length == fieldsB.length &&
    (true /: (fieldsA zip fieldsB)){ case (res, (aa, bb)) =>
      if(((aa.getModifiers & STATIC) != 0) || ((bb.getModifiers & STATIC) != 0)) { res  // ignore
      } else if(((aa.getModifiers & (PRIVATE | PROTECTED)) == 0) && ((bb.getModifiers & (PRIVATE | PROTECTED)) == 0)) {
        res && aa == bb && aa.get(a) ==:== bb.get(b)
      } else if((mapA contains aa.getName) && (mapB contains bb.getName)) {
        res && mapA(aa.getName).invoke(a) == mapB(aa.getName).invoke(b)
      } else res
    }
  }
}

trait EqualBad {
  override def equals(other: Any) = false
}

case class MyCaseClass(x: Int, y: List[Int]) extends EqualBad

MyCaseClass(1, List(1, 2)) ==:== MyCaseClass(1, List(1, 2)) // true
MyCaseClass(1, List(1, 2)) ==:== MyCaseClass(1, List(2, 2)) // false
MyCaseClass(1, List(1, 2)) ==:== MyCaseClass(2, List(1, 2)) // false
MyCaseClass(1, List(1, 2)) == MyCaseClass(1, List(1, 2))  // false

You can even make it more recursive by changing the last == in the method ReallyEqual to ==:==

Careful: This will not work for integers, because Int does not have any field or methods. Ex: 1 ==:== 2 will return true.

Mikaël Mayer
  • 10,425
  • 6
  • 64
  • 101