Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala case class copy with dynamic named parameter

Tags:

scala

For scala case class with number of parameters (21!!)

e.g. case class Car(type: String, brand: String, door: Int ....) where type = jeep, brand = toyota, door = 4 ....etc

And there is a copy method which allow override with named parameter: Car.copy(brand = Kia) where would become type = jeep, brand = Kia, door = 2...etc

My question is, is there anyway I can provide the named parameter dynamically?

def copyCar(key: String, name: String) = {
  Car.copy("key" = "name") // this is something I make up and want to see if would work
}

Is scala reflection library could provide a help here?

The reason I am using copy method is that I don't want to repeat the 21 parameters assignment every time when I create a case class which only have 1 or 2 parameter changed.

Many Thanks!

like image 899
Joyfulvillage Avatar asked May 30 '26 23:05

Joyfulvillage


1 Answers

FWIW, I've just implemented a Java reflection version: CaseClassCopy.scala. I tried a TypeTag version but it wasn't that useful; TypeTag was too restrictive for this purpose.

  def copy(o: AnyRef, vals: (String, Any)*) = {
    val copier = new Copier(o.getClass)
    copier(o, vals: _*)
  }

  /**
   * Utility class for providing copying of a designated case class with minimal overhead.
   */
  class Copier(cls: Class[_]) {
    private val ctor = cls.getConstructors.apply(0)
    private val getters = cls.getDeclaredFields
      .filter {
      f =>
        val m = f.getModifiers
        Modifier.isPrivate(m) && Modifier.isFinal(m) && !Modifier.isStatic(m)
    }
      .take(ctor.getParameterTypes.size)
      .map(f => cls.getMethod(f.getName))

    /**
     * A reflective, non-generic version of case class copying.
     */
    def apply[T](o: T, vals: (String, Any)*): T = {
      val byIx = vals.map {
        case (name, value) =>
          val ix = getters.indexWhere(_.getName == name)
          if (ix < 0) throw new IllegalArgumentException("Unknown field: " + name)
          (ix, value.asInstanceOf[Object])
      }.toMap

      val args = (0 until getters.size).map {
        i =>
          byIx.get(i)
            .getOrElse(getters(i).invoke(o))
      }
      ctor.newInstance(args: _*).asInstanceOf[T]
    }
  }
like image 119
Peter L Avatar answered Jun 02 '26 14:06

Peter L



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!