假设我有这个示例案例类
case class Test(key1: Int, key2: String, key3: String)
Run Code Online (Sandbox Code Playgroud)
我有一张地图
myMap = Map("k1" -> 1, "k2" -> "val2", "k3" -> "val3")
Run Code Online (Sandbox Code Playgroud)
我需要在代码的几个位置将此映射转换为我的case类,如下所示:
myMap.asInstanceOf[Test]
Run Code Online (Sandbox Code Playgroud)
最简单的方法是什么?我可以以某种方式使用隐含的吗?
whe*_*ies 24
两种方式优雅地做到这一点.第一种是使用a unapply,第二种是使用带类型类的隐式类(2.10 +)来为你进行转换.
1)unapply是编写这种转换的最简单,最直接的方法.它没有做任何"魔术",如果使用IDE,可以很容易地找到它.请注意,执行此类操作可能会使您的伴随对象混乱,并导致您的代码在您可能不需要的位置发布依赖项:
object MyClass{
def unapply(values: Map[String,String]) = try{
Some(MyClass(values("key").toInteger, values("next").toFloat))
} catch{
case NonFatal(ex) => None
}
}
Run Code Online (Sandbox Code Playgroud)
哪个可以这样使用:
val MyClass(myInstance) = myMap
Run Code Online (Sandbox Code Playgroud)
要小心,因为如果不完全匹配会抛出异常.
2)使用类型类创建一个隐式类会为您创建更多的样板文件,但也允许有很大的空间来扩展相同的模式以应用于其他案例类:
implicit class Map2Class(values: Map[String,String]){
def convert[A](implicit mapper: MapConvert[A]) = mapper conv (values)
}
trait MapConvert[A]{
def conv(values: Map[String,String]): A
}
Run Code Online (Sandbox Code Playgroud)
作为一个例子,你会做这样的事情:
object MyObject{
implicit val new MapConvert[MyObject]{
def conv(values: Map[String, String]) = MyObject(values("key").toInt, values("foo").toFloat)
}
}
Run Code Online (Sandbox Code Playgroud)
然后可以像上面描述的那样使用它:
val myInstance = myMap.convert[MyObject]
Run Code Online (Sandbox Code Playgroud)
如果不能进行转换,则抛出异常.使用此模式在a Map[String, String]到任何对象之间进行转换只需要另一个隐式(并且隐含在范围内).
jks*_*der 14
这是一个使用Scala反射(Scala 2.10及更高版本)的替代非样板方法,不需要单独编译的模块:
import org.specs2.mutable.Specification
import scala.reflect._
import scala.reflect.runtime.universe._
case class Test(t: String, ot: Option[String])
package object ccFromMap {
def fromMap[T: TypeTag: ClassTag](m: Map[String,_]) = {
val rm = runtimeMirror(classTag[T].runtimeClass.getClassLoader)
val classTest = typeOf[T].typeSymbol.asClass
val classMirror = rm.reflectClass(classTest)
val constructor = typeOf[T].decl(termNames.CONSTRUCTOR).asMethod
val constructorMirror = classMirror.reflectConstructor(constructor)
val constructorArgs = constructor.paramLists.flatten.map( (param: Symbol) => {
val paramName = param.name.toString
if(param.typeSignature <:< typeOf[Option[Any]])
m.get(paramName)
else
m.get(paramName).getOrElse(throw new IllegalArgumentException("Map is missing required parameter named " + paramName))
})
constructorMirror(constructorArgs:_*).asInstanceOf[T]
}
}
class CaseClassFromMapSpec extends Specification {
"case class" should {
"be constructable from a Map" in {
import ccFromMap._
fromMap[Test](Map("t" -> "test", "ot" -> "test2")) === Test("test", Some("test2"))
fromMap[Test](Map("t" -> "test")) === Test("test", None)
}
}
}
Run Code Online (Sandbox Code Playgroud)
Jonathan Chow实现了一个Scala宏(专为Scala 2.11设计),它概括了这种行为并消除了样板.
http://blog.echo.sh/post/65955606729/exploring-scala-macros-map-to-case-class-conversion
import scala.reflect.macros.Context
trait Mappable[T] {
def toMap(t: T): Map[String, Any]
def fromMap(map: Map[String, Any]): T
}
object Mappable {
implicit def materializeMappable[T]: Mappable[T] = macro materializeMappableImpl[T]
def materializeMappableImpl[T: c.WeakTypeTag](c: Context): c.Expr[Mappable[T]] = {
import c.universe._
val tpe = weakTypeOf[T]
val companion = tpe.typeSymbol.companionSymbol
val fields = tpe.declarations.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor ? m
}.get.paramss.head
val (toMapParams, fromMapParams) = fields.map { field ?
val name = field.name
val decoded = name.decoded
val returnType = tpe.declaration(name).typeSignature
(q"$decoded ? t.$name", q"map($decoded).asInstanceOf[$returnType]")
}.unzip
c.Expr[Mappable[T]] { q"""
new Mappable[$tpe] {
def toMap(t: $tpe): Map[String, Any] = Map(..$toMapParams)
def fromMap(map: Map[String, Any]): $tpe = $companion(..$fromMapParams)
}
""" }
}
}
Run Code Online (Sandbox Code Playgroud)