Scala case class generated field value

2.1k Views Asked by At

I have an existing Scala application and it uses case classes which are then persisted in MongoDB. I need to introduce a new field to a case class but the value of it is derived from existing field.

For example, there is phone number and I want to add normalised phone number while keeping the original phone number. I'll update the existing records in MongoDB but I would need to add this normalisation feature to existing save and update code.

So, is there any nice shortcut in Scala to add a "hook" to a certain field of a case class? For example, in Java one could modify setter of the phone number.

Edit:

The solution in Christian's answer works fine alone but in my case I have defaults for fields (I think because of Salat...)

case class Person(name: String = "a", phone: Option[String] = None, normalizedPhone: Option[String] = None)

object Person {
  def apply(name: String, phone: Option[String]): Person = Person(name, phone, Some("xxx" + phone.getOrElse("")))
}

And if use something like: Person(phone = Some("s"))

I'll get: Person = Person(a,Some(s),None)

3

There are 3 best solutions below

1
Christian On BEST ANSWER

You can define an apply method in the companion object:

case class Person(name: String, phone: String, normalizedPhone: String)

object Person {
  def apply(name: String, phone: String): Person =  Person(name, phone, "xxx" + phone)
}

Then, in the repl:

scala> Person("name", "phone")
res3: Person = Person(name,phone,xxxphone)
3
dzs On

You could add methods to the case class that would contain the transforming logic from existing fields. For example:

case class Person(name: String, phone: String) {
  def normalizedPhone = "+40" + phone
}

Then you can use the method just as if it was a field:

val p1 = new Person("Joe", "7234")
println(p1.normalizedPhone) // +407234
0
Robby Cornelissen On

I think this comes close to what you need. Since you can't override the generated mutator, prefix the existing field with an underscore, make it private, and then write the accessor and mutator methods for the original field name. After that, you only need an extra line in the constructor to accommodate for constructor-based initialization of the field.

case class Test(private var _phoneNumber: String, var formatted: String = "") {
  phoneNumber_=(_phoneNumber)

  def phoneNumber = _phoneNumber

  def phoneNumber_=(phoneNumber: String) {
    _phoneNumber = phoneNumber
    formatted = "formatted" + phoneNumber
  }
}

object Test {
  def main(args: Array[String]) {
    var t = Test("09048751234")

    println(t.phoneNumber)
    println(t.formatted)

    t.phoneNumber = "08068745963"

    println(t.phoneNumber)
    println(t.formatted)
  }
}