2011-05-31 33 views
16

如果我有一個Map[String,String]("url" -> "xxx", "title" -> "yyy"),有沒有辦法一般地將它轉換爲case class Image(url:String, title:String)如何將地圖變換爲Scala中的案例類?

我可以寫一個幫手:

object Image{ 
    def fromMap(params:Map[String,String]) = Image(url=params("url"), title=params("title")) 
} 

,但有沒有辦法一般的地圖,任何情況下,類寫這一次?

回答

8

首先,如果你只是想縮短你的代碼,你可以做一些安全的選擇。同伴對象可以被視爲一個功能,所以你可以使用這樣的事情:

def build2[A,B,C](m: Map[A,B], f: (B,B) => C)(k1: A, k2: A): Option[C] = for { 
    v1 <- m.get(k1) 
    v2 <- m.get(k2) 
} yield f(v1, v2) 

build2(m, Image)("url", "title") 

這將返回一個包含結果的選項。另外,您可以使用ApplicativeBuilder S IN Scalaz在內部做幾乎相同,但有一個更好的語法:

import scalaz._, Scalaz._ 
(m.get("url") |@| m.get("title"))(Image) 

如果你真的需要通過反射來這樣做,那麼最簡單的方法是使用Paranamer(如電梯 - 框架確實)。 Paranamer可以通過檢查字節碼來恢復參數名稱,所以性能會受到影響,並且由於類加載器問題(例如,REPL),它不適用於所有環境。如果你限制自己的類只有String構造函數的參數,那麼你可以做這樣的:

val pn = new CachingParanamer(new BytecodeReadingParanamer) 

def fill[T](m: Map[String,String])(implicit mf: ClassManifest[T]) = for { 
    ctor <- mf.erasure.getDeclaredConstructors.filter(m => m.getParameterTypes.forall(classOf[String]==)).headOption 
    parameters = pn.lookupParameterNames(ctor) 
} yield ctor.newInstance(parameters.map(m): _*).asInstanceOf[T] 

val img = fill[Image](m) 

(注意這個例子可以選擇一個默認的構造函數,因爲它不會檢查你會想參數計數千萬)

+0

build2的類型參數與字段數成正比。我不這麼整潔,我想。 – 2014-10-09 21:02:01

-2

這是無法完成的,因爲您需要獲取伴隨對象的apply方法的參數名稱,並且它們不能通過反射獲得。如果你有很多這樣的case類,你可以解析它們的聲明並生成fromMap方法。

+2

它們不是通過標準的Java反射,但您可以試試你的運氣解析['ScalaSignature'] (http://www.scala-lang.org/sid/10)bytes ... – 2011-05-31 08:01:42

2

不是一個完整的回答你的問題,而是一個開始......

這是可以做到的,但它可能會比你想象的更棘手。每個生成的Scala類都使用Java註釋ScalaSignature進行註釋,其中的bytes成員可以被解析,以便爲您提供所需的元數據(包括參數名稱)。然而,這個簽名的格式不是API,所以你需要自己解析它(並且可能會改變你用每個新的主要Scala版本解析它的方式)。

也許最好的開始是lift-json庫,它能夠根據JSON數據創建case類的實例。

更新:我想提JSON實際使用Paranamer要做到這一點,因此可能無法分析字節ScalaSignature ...這使得非Scala類這種技術的工作了。

更新2:請參閱Moritz's answer相反,誰比我更瞭解情況。

+0

那麼爲什麼不創建json數據並讓lift-json完成剩下的工作?這樣他就不必使用每個新版本的scala自行更新它,並且不必分析ScalaSignature字節。當然,績效並不理想,但這對OP來說可能不是問題。我錯過了什麼嗎? – 2011-05-31 08:51:05

+0

@Kim嘿,這是一個很好的方法 - 如果OP願意爲此目的創建JSON。另外,也許有可能更直接地使用一個'Map [String,String]作爲輸入... – 2011-05-31 08:55:51

+0

感謝您的提示重新使用lift-json。從Map到JSON字符串到lift-json解析到case類似乎很多不必要的序列化/反序列化處理。 – 2011-06-01 19:08:41

6

使用內置的Scala/Java反射這裏有一個解決方案:

def createCaseClass[T](vals : Map[String, Object])(implicit cmf : ClassManifest[T]) = { 
     val ctor = cmf.erasure.getConstructors().head 
     val args = cmf.erasure.getDeclaredFields().map(f => vals(f.getName)) 
     ctor.newInstance(args : _*).asInstanceOf[T] 
    } 

要使用它:

val image = createCaseClass[Image](Map("url" -> "xxx", "title" -> "yyy")) 
+0

這是一個有趣的方法,但是如何在實例化類時避免「參數類型不匹配」異常? – bachr 2016-12-08 17:20:44

+0

這是我見過的這個問題的最簡潔的答案。但是它使用現在已被棄用的API。這很容易通過使用「ClassTag」而不是「ClassManifest」和「runtimeClass」而不是「擦除」來更新 – 2017-06-27 21:30:00

0

有一個破解你可以將地圖轉換成json然後轉換爲case類。 我用噴霧JSON

import spray.json._ 

object MainClass2 extends App { 
    val mapData: Map[Any, Any] = 
    Map(
     "one" -> "1", 
     "two" -> 2, 
     "three" -> 12323232123887L, 
     "four" -> 4.4, 
     "five" -> false 
    ) 

    implicit object AnyJsonFormat extends JsonFormat[Any] { 
    def write(x: Any): JsValue = x match { 
     case int: Int   => JsNumber(int) 
     case long: Long   => JsNumber(long) 
     case double: Double  => JsNumber(double) 
     case string: String  => JsString(string) 
     case boolean: Boolean if boolean => JsTrue 
     case boolean: Boolean if !boolean => JsFalse 
    } 
    def read(value: JsValue): Any = value match { 
     case JsNumber(int) => int.intValue() 
     case JsNumber(long) => long.longValue() 
     case JsNumber(double) => double.doubleValue() 
     case JsString(string) => string 
     case JsTrue  => true 
     case JsFalse  => false 
    } 
    } 

    import ObjJsonProtocol._ 
    val json = mapData.toJson 
    val result: TestObj = json.convertTo[TestObj] 
    println(result) 

} 

final case class TestObj(one: String, two: Int, three: Long, four: Double, five: Boolean) 

object ObjJsonProtocol extends DefaultJsonProtocol { 
    implicit val objFormat: RootJsonFormat[TestObj] = jsonFormat5(TestObj) 
} 

並使用SBT構建這種依賴關係:

"io.spray"   %% "spray-json"  % "1.3.3" 
相關問題