比方說,我有這樣的例子案例類斯卡拉:轉換地圖案例類
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:重點是避免反射,你說答案很複雜,我說你的答案是脆弱的。 –