Scala:从嵌套案例类到扁平案例类

时间:2018-09-26 09:12:44

标签: scala

问题是:

如何构建一个通用函数,该函数可以采用由其他案例类组成的任何案例类,然后将其平整为一个案例类,并将所有案例类中的所有值都组合到一个案例类中? < / p>

例如,我想像这样转换嵌套案例类

case class A(first: String, second: String)
case class B(value: String)

case class Nested(a: A, b: B)

像这样的扁平案例类

case class Flatten(aFirst: String, aSecond: String, bValue: String)

但是我想避免构建自己的构造函数(或手动创建函数),如下所示:

object Flatten {

  def apply(nested: Nested): Flatten = {
    Flatten(nested.a.first, nested.a.second, nested.b.value)
  }
}

注意:在实际用例中,用例类更为复杂,我想在不同的用例类上多次使用该方法。

1 个答案:

答案 0 :(得分:2)

假设目标案例类字段名称具有预定义格式,则可以使用反射api。看看例子

import scala.reflect.runtime.universe._

class Converter(any: Any) {
  private val rm = runtimeMirror(any.getClass.getClassLoader)

  private def nameToPath(name: String, pathElem: String = "", pathElems: List[String] = List()): List[String] =
    if (name.isEmpty) pathElems :+ pathElem.toLowerCase()
    else if (name.head.isUpper) nameToPath(name.tail, name.head.toString, pathElems :+ pathElem)
    else nameToPath(name.tail, pathElem + name.head, pathElems)

  private def valueByPath(v: Any, pathElems: List[String]): Any =
    if (pathElems.isEmpty) v
    else {
      val im = rm.reflect(v)
      val fieldName = TermName(pathElems.head)
      val field = im.symbol.info.member(fieldName).asTerm
      val value = im.reflectField(field).get
      valueByPath(value, pathElems.tail)
    }

  def convertTo[T: TypeTag]: T = {
    val target = typeOf[T]
    val fieldNames = target.decls.sorted.collect {
      case m: MethodSymbol if m.isCaseAccessor => m
    }

    val paths = fieldNames.map(s => nameToPath(s.name.toString))
    val values = paths.map(valueByPath(any, _))

    val constructorSymbol = target.decl(termNames.CONSTRUCTOR)

    val defaultConstructor = constructorSymbol match {
      case cs: MethodSymbol => cs
      case ts: TermSymbol =>
        ts.alternatives.collectFirst {
          case ms: MethodSymbol if ms.isPrimaryConstructor => ms
        }.get
    }

    val cs = target.typeSymbol.asClass
    val cm = rm.reflectClass(cs)
    val constructor = cm.reflectConstructor(defaultConstructor)
    constructor(values: _*).asInstanceOf[T]
  }
}

implicit class AnyOps(any: Any) {
  def to[T: TypeTag]: T = new Converter(any).convertTo[T]
}

使用

val a = A("1", "2")
val b = B("3")
val n = Nested(a, b)

val r = n.to[Flatten]

输出

r: Flatten = Flatten(1,2,3)