如果我有Map[String,String]("url" -> "xxx", "title" -> "yyy")
,是否有办法将其一般转换为case class Image(url:String, title:String)
?
我可以写一个帮手:
object Image{
def fromMap(params:Map[String,String]) = Image(url=params("url"), title=params("title"))
}
但对于任何案例类的地图,有没有办法一次性写一次这个?
答案 0 :(得分: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")
这将返回包含结果的选项。或者你可以使用Scalaz中的ApplicativeBuilder
,它在内部做的几乎相同,但语法更好:
import scalaz._, Scalaz._
(m.get("url") |@| m.get("title"))(Image)
如果你真的需要通过反射来做到这一点,那么最简单的方法就是使用Paranamer(就像Lift-Framework那样)。 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)
(请注意,此示例可以选择默认构造函数,因为它不会检查您想要执行的参数计数)
答案 1 :(得分: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"))
答案 2 :(得分:2)
不是你问题的完整答案,而是一个开始......
可以做到,但它可能会比你想象的更棘手。每个生成的Scala类都使用Java注释ScalaSignature
进行注释,可以解析其bytes
成员以提供您需要的元数据(包括参数名称)。但是,此签名的格式不是API,因此您需要自己解析它(并且可能会改变您使用每个新的主要Scala版本解析它的方式)。
也许最好的起点是lift-json库,它能够根据JSON数据创建案例类的实例。
更新:我认为lift-json实际上使用Paranamer来执行此操作,因此可能无法解析ScalaSignature
的字节...这使得此技术可用于非Scala课程也是如此。
更新2 :相反,请参阅Moritz's answer,告知谁比我更了解情况。
答案 3 :(得分:0)
您可以将地图转换为json然后转换为案例类。 我用过spray-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 build中使用此依赖项:
"io.spray" %% "spray-json" % "1.3.3"
答案 4 :(得分:-2)
这是不可能的,因为您需要获取伴随对象的apply方法的参数名称,并且它们不能通过反射获得。如果你有很多这些case类,你可以解析它们的声明并生成fromMap方法。