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

  • 本文关键字:案例 嵌套 Scala scala
  • 更新时间 :
  • 英文 :


问题是:

如何构建一个通用函数,该函数可以采用由其他事例类组成的任何事例类,并将其平展为一个事例类,其中包含组合事例类中每个事例类的所有值?

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

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

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

假设目标案例类字段名称具有预定义的格式,则可以使用反射 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)

最新更新