scala 循环访问案例类数据成员

fzwojiic  于 2022-11-09  发布在  Scala
关注(0)|答案(4)|浏览(118)

我正在用MongoDB编写一个play2.1应用程序,我的模型对象有点广泛。在更新数据库中的条目时,我需要将来自表单的临时对象与数据库中的对象进行比较,以便构建更新查询(并记录更改)。
我正在寻找一种方法,一般采取2个示例,并得到他们的不同。迭代每个数据成员是很长的、硬编码的,而且容易出错(如果a.firstName.equalsIgnoreCase(b.first stName)),所以我正在寻找一种方法来迭代所有数据成员并横向比较它们(名称->值的Map就可以了,或者我可以信任的列表每次都以相同的顺序枚举数据成员)。
有什么好主意吗?

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())
)

下面的三个解决方案都很棒,但我仍然无法获得该字段的名称,对吗?这意味着我可以记录更改,但不能记录它影响的领域...

dldeef67

dldeef671#

也许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)
guicsvcw

guicsvcw2#

扩展@Malte_Schwerhoff的答案,您可能会创建一个递归的diff方法,该方法不仅生成差异的索引,而且将它们Map到该索引处的新值-或者在嵌套的Product类型的情况下,Map到子Product差异的Map:

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
ogq8wdun

ogq8wdun3#

如果要使用非标准相等(如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
ffscu2ro

ffscu2ro4#

如果您想访问字段名,可以使用Java的反射API。在本例中,您可以使用getDeclaredFields方法访问声明的字段并迭代这些字段。如果您随后想要访问字段的值更改,则需要将其设置为可访问(setAccessible方法),因为默认情况下,所有类参数都实现为具有公共访问器的私有字段。

val c = C(1, "C", 'c'))
for(field <- c.getClass.getDeclaredFields) {
  println(field.getName)
  field.get(c)
}

相关问题