比方说,我有这样的例子案例类斯卡拉:转换地图案例类
case class Test(key1: Int, key2: String, key3: String)
而且我有一张地图
myMap = Map("k1" -> 1, "k2" -> "val2", "k3" -> "val3")
我需要这个地图转化为我的案例类的几个地方的代码,如下所示:
myMap.asInstanceOf[Test]
这样做最简单的方法是什么?我可以以某种方式使用隐式的呢?
比方说,我有这样的例子案例类斯卡拉:转换地图案例类
case class Test(key1: Int, key2: String, key3: String)
而且我有一张地图
myMap = Map("k1" -> 1, "k2" -> "val2", "k3" -> "val3")
我需要这个地图转化为我的案例类的几个地方的代码,如下所示:
myMap.asInstanceOf[Test]
这样做最简单的方法是什么?我可以以某种方式使用隐式的呢?
这样做的两种方法优雅。第一种是使用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]
到任何对象之间的这种模式转换只需要另一个隐式(并且隐含在范围内)。
@flavian true,但我相信他会想要如果不是转换,在调用它的地方是一个意想不到的结果,至少我会想要这个异常。 – wheaties
我不喜欢这段代码,但我想如果可以获取地图值转换为元组,然后使用案例类的构造函数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。就像我说的,不是很好,但它表明它是可能的。
对于这个解决方案,我只能用'''myMap.map(_._ 2)'替换''' ''''myMap.values''' –
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)
}
""" }
}
}
下面是一个使用的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)
}
}
}
这应该是可以接受的解决方案,一个函数适用于任何类,它不需要宏,也不需要外部依赖,并且使用所有支持的功能。 。 –
一个小建议。fromMap当前返回Any。将最后一行改为constructorMirror(constructorArgs:_ *)。asInstanceOf [T]使它返回正确的类型。 –
另一个意见要注意。如果您的案例类有一个Option [String]参数,则此代码需要Map包含一个字符串。如果它实际上包含Option [String],那么最终会出现问题。删除“if”语句会改变这一点。取决于你想要的。 –
commons.mapper.Mappers.mapToBean[CaseClassBean](map)
尽管这个链接可能回答这个问题,但最好在这里包含答案的重要部分,并提供供参考的链接。如果链接页面更改,则仅链接答案可能会失效。 –
我不明白为什么的答案是如此复杂。如果你想要一个普通函数def map2Test(m:Map [String,Any])= Test(m(“k1”)。asInstanceOf [Int],m(“k2”)...)将地图转换为任何案例类,你可能必须使用反射 –
@RobN:重点是避免反射,你说答案很复杂,我说你的答案是脆弱的。 –