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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With