2011-05-31 57 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" 
相关问题