2013-12-19 243 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

儘管這個鏈接可能回答這個問題,但最好在這裏包含答案的重要部分,並提供供參考的鏈接。如果鏈接頁面更改,則僅鏈接答案可能會失效。 –