Reputation: 21
Is there a way to copy a case class in scala using a map of fields and their new values?
I tried a way that included reflection so I would like to avoid that
def copyWithMap(fieldNameValue: Map[String, Option[AnyREF]]): T
when applied to a case class this should make a copy and update the fields that need updating. (specified in the map)
Upvotes: 0
Views: 1559
Reputation: 3474
If you know what you have to update before you start, you can do something like this:
scala> case class Person(name: String, age: Int, eyeColour: String)
defined class Person
scala> val p1 = Person("Bill", 24, "blue")
p1: Person = Person(Bill,24,blue)
scala> val p2 = p1.copy(name = "Ben", eyeColour = "brown")
p2: Person = Person(Ben,24,brown)
If you want to make it more generic, maybe something like this would work (setField taken from Duncan McGregor's answer in the linked post and put in implicit class):
implicit class Modify[T](i: T) {
def modify(m: Map[String, Any]): T = {
for ((name, value) <- m) setField(name, value)
i
}
def setField(fieldName: String, fieldValue: Any) = {
i.getClass.getDeclaredFields.find(_.getName == fieldName) match {
case Some(field) =>
field.setAccessible(true)
field.set(i, fieldValue)
case None =>
throw new IllegalArgumentException(s"No field named $fieldName")
}
}
}
case class Person(name: String, age: Int, eyeColour: String)
val p1 = Person("Bill", 24, "blue")
val p2 = p1.copy().modify(Map("name" -> "Ben", "eyeColour" -> "brown"))
// p2: Person = Person(Ben,24,brown)
p1
// res0: Person = Person(Bill,24,blue)
Upvotes: 1
Reputation: 27356
If you want to do this for any generic object then you need some way of mapping the compile-time name of a field to the run-time location of that field in the object. The only built-in mechanism for this is reflection, so you can't do what you want without using reflection (or implementing your own generic reflection mechanism, which seems pointless).
Upvotes: 2