2013-12-19 177 views
19

比方说,我有这样的例子案例类斯卡拉:转换地图案例类

case class Test(key1: Int, key2: String, key3: String) 

而且我有一张地图

myMap = Map("k1" -> 1, "k2" -> "val2", "k3" -> "val3") 

我需要这个地图转化为我的案例类的几个地方的代码,如下所示:

myMap.asInstanceOf[Test] 

这样做最简单的方法是什么?我可以以某种方式使用隐式的呢?

+1

我不明白为什么的答案是如此复杂。如果你想要一个普通函数def map2Test(m:Map [String,Any])= Test(m(“k1”)。asInstanceOf [Int],m(“k2”)...)将地图转换为任何案例类,你可能必须使用反射 –

+1

@RobN:重点是避免反射,你说答案很复杂,我说你的答案是脆弱的。 –

回答

21

这样做的两种方法优雅。第一种是使用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 
    } 
} 

哪位能像这样被使用:

val MyClass(myInstance) = myMap 

要小心,因为如果不完全匹配,它会抛出异常。

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 
} 

,并作为一个例子,你倒是做这样的事情:

object MyObject{ 
    implicit val new MapConvert[MyObject]{ 
    def conv(values: Map[String, String]) = MyObject(values("key").toInt, values("foo").toFloat) 
    } 
} 

这可能是你有上述那么可以只用:

val myInstance = myMap.convert[MyObject] 

如果不能进行转换,则会抛出异常。使用Map[String, String]到任何对象之间的这种模式转换只需要另一个隐式(并且隐含在范围内)。

+0

@flavian true,但我相信他会想要如果不是转换,在调用它的地方是一个意想不到的结果,至少我会想要这个异常。 – wheaties

2

我不喜欢这段代码,但我想如果可以获取地图值转换为元组,然后使用案例类的构造函数tupled。这将是这个样子:

val myMap = Map("k1" -> 1, "k2" -> "val2", "k3" -> "val3")  
val params = Some(myMap.map(_._2).toList).flatMap{ 
    case List(a:Int,b:String,c:String) => Some((a,b,c)) 
    case other => None 
}  
val myCaseClass = params.map(Test.tupled(_)) 
println(myCaseClass) 

你必须要小心,以确保值列表恰好是3个元素,他们是正确的类型。如果不是,你最终会得到一个None。就像我说的,不是很好,但它表明它是可能的。

+1

对于这个解决方案,我只能用'''myMap.map(_._ 2)'替换''' ''''myMap.values''' –

3

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

下面是一个使用的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) 
    } 
    } 
} 
+0

这应该是可以接受的解决方案,一个函数适用于任何类,它不需要宏,也不需要外部依赖,并且使用所有支持的功能。 。 –

+1

一个小建议。fromMap当前返回Any。将最后一行改为constructorMirror(constructorArgs:_ *)。asInstanceOf [T]使它返回正确的类型。 –

+1

另一个意见要注意。如果您的案例类有一个Option [String]参数,则此代码需要Map包含一个字符串。如果它实际上包含Option [String],那么最终会出现问题。删除“if”语句会改变这一点。取决于你想要的。 –

0
commons.mapper.Mappers.mapToBean[CaseClassBean](map) 

详情:https://github.com/hank-whu/common4s

+1

尽管这个链接可能回答这个问题,但最好在这里包含答案的重要部分,并提供供参考的链接。如果链接页面更改,则仅链接答案可能会失效。 –