Я пишу приложение play2.1 с mongodb, и мой объект модели немного широк. при обновлении записи в БД мне нужно сравнить временный объект, исходящий из формы, с тем, что в БД, поэтому я могу построить запрос обновления (и зарегистрировать изменения).
Я ищу способ в общем случае взять 2 экземпляра и получить их. итерация по каждому элементу данных длинна, жестко закодирована и подвержена ошибкам (если a.firstName.equalsIgnoreCase(b.firstName)), поэтому я ищу способ перебрать все элементы данных и сравнить их по горизонтали (карта имени → значение, или список, которому я могу доверять, чтобы каждый раз перечислять члены данных в одном порядке).
любые идеи?
case class Customer(
id: Option[BSONObjectID] = Some(BSONObjectID.generate),
firstName: String,
middleName: String,
lastName: String,
address: List[Address],
phoneNumbers: List[PhoneNumber],
email: String,
creationTime: Option[DateTime] = Some(DateTime.now()),
lastUpdateTime: Option[DateTime] = Some(DateTime.now())
)
все три решения ниже велики, но я все еще не могу получить имя поля, не так ли? это означает, что я могу зарегистрировать изменение, но не в каком поле это повлияло...
Ответ 1
Развернув ответ на @Malte_Schwerhoff, вы можете создать рекурсивный метод diff, который не только сгенерировал индексы различий, но и сопоставил их с новым значением в этом индексе - или в случае вложенных типов Продуктов - карту Различия между продуктами:
def diff(orig: Product, update: Product): Map[Int, Any] = {
assert(orig != null && update != null, "Both products must be non-null")
assert(orig.getClass == update.getClass, "Both products must be of the same class")
val diffs = for (ix <- 0 until orig.productArity) yield {
(orig.productElement(ix), update.productElement(ix)) match {
case (s1: String, s2: String) if (!s1.equalsIgnoreCase(s2)) => Some((ix -> s2))
case (s1: String, s2: String) => None
case (p1: Product, p2: Product) if (p1 != p2) => Some((ix -> diff(p1, p2)))
case (x, y) if (x != y) => Some((ix -> y))
case _ => None
}
}
diffs.flatten.toMap
}
Развертывание вариантов использования из этого ответа:
case class A(x: Int, y: String)
case class B(a: A, b: AnyRef, c: Any)
val a1 = A(4, "four")
val a2 = A(4, "Four")
val a3 = A(4, "quatre")
val a4 = A(5, "five")
val b1 = B(a1, null, 6)
val b2 = B(a1, null, 7)
val b3 = B(a2, a2, a2)
val b4 = B(a4, null, 8)
println(diff(a1, a2)) // Map()
println(diff(a1, a3)) // Map(0 -> 5)
println(diff(a1, a4)) // Map(0 -> 5, 1 -> five)
println(diff(b1, b2)) // Map(2 -> 7)
println(diff(b1, b3)) // Map(1 -> A(4,four), 2 -> A(4,four))
println(diff(b1, b4)) // Map(0 -> Map(0 -> 5, 1 -> five), 2 -> 8l
Ответ 2
Возможно, productIterator
- это то, что вы хотели:
scala> case class C(x: Int, y: String, z: Char)
defined class C
scala> val c1 = C(1, "2", 'c')
c1: C = C(1,2,c)
scala> c1.productIterator.toList
res1: List[Any] = List(1, 2, c)
Ответ 3
Вы можете использовать итератор продукта и сопоставлять элементы, если хотите использовать нестандартное равенство, например String.equalsIgnoreCase
.
def compare(p1: Product, p2: Product): List[Int] = {
assert(p1 != null && p2 != null, "Both products must be non-null")
assert(p1.getClass == p2.getClass, "Both products must be of the same class")
var idx = List[Int]()
for (i <- 0 until p1.productArity) {
val equal = (p1.productElement(i), p2.productElement(i)) match {
case (s1: String, s2: String) => s1.equalsIgnoreCase(s2)
case (x, y) => x == y
}
if (!equal) idx ::= i
}
idx.reverse
}
Варианты использования:
case class A(x: Int, y: String)
case class B(a: A, b: AnyRef, c: Any)
val a1 = A(4, "four")
val a2 = A(4, "Four")
val a3 = A(5, "five")
val b1 = B(a1, null, 6)
val b2 = B(a1, null, 7)
val b3 = B(a2, a2, a2)
println(compare(a1, a2)) // List()
println(compare(a1, a3)) // List(0, 1)
println(compare(b1, b2)) // List(2)
println(compare(b2, b3)) // List(0, 1, 2)
// println(compare(a1, b1)) // assertion failed