Class 如何将映射转换为Scala中的case类?
如果我有一个Class 如何将映射转换为Scala中的case类?,class,scala,map,case,Class,Scala,Map,Case,如果我有一个映射[String,String](“url”->“xxx”,“title”->“yyy”),有没有一种方法可以将它转换成案例类图像(url:String,title:String) 我可以写一个助手: object Image{ def fromMap(params:Map[String,String]) = Image(url=params("url"), title=params("title")) } 但是有没有一种方法可以为映射到任何case类的映射通用地编写一次呢?
映射[String,String](“url”->“xxx”,“title”->“yyy”)
,有没有一种方法可以将它转换成案例类图像(url:String,title:String)
我可以写一个助手:
object Image{
def fromMap(params:Map[String,String]) = Image(url=params("url"), title=params("title"))
}
但是有没有一种方法可以为映射到任何case类的映射通用地编写一次呢?这是无法做到的,因为您需要获取伴随对象的apply方法的参数名,而它们只是无法通过反射获得。如果您有很多这样的case类,您可以解析它们的声明并生成fromMap方法。这不是问题的完整答案,而是一个开始 这是可以做到的,但它可能会变得比你想象的更棘手。每个生成的Scala类都使用Java注释进行注释,可以解析其
字节
成员,为您提供所需的元数据(包括参数名称)。但是,此签名的格式不是API,因此您需要自己解析它(并且可能会在每个新的主要Scala版本中更改解析它的方式)
也许最好从库开始,它能够基于JSON数据创建案例类的实例
更新:我认为lift json实际上是用来做这件事的,因此可能无法解析ScalaSignature
…的字节,这使得这种技术也适用于非Scala类
更新2:看看谁比我更了解情况。首先,如果您只想缩短代码,可以使用一些安全的替代方法。可以将伴生对象视为函数,因此可以使用如下内容:
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")
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)
如果你真的需要通过反射来实现这一点,那么最简单的方法就是使用Paranamer(就像提升框架一样)。Paranamer可以通过检查字节码来恢复参数名,这样会影响性能,并且由于类加载器问题(例如REPL),它无法在所有环境中工作。如果您将自己限制为仅具有String
构造函数参数的类,则可以这样做:
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")
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)
val pn=new CachingParanamer(new BytecodeReadingParanamer)
def fill[T](m:Map[String,String])(隐式mf:ClassManifest[T])=for{
ctor m.getParameterTypes.forall(classOf[String]==).headOption
参数=pn.lookupParameterNames(ctor)
}yield ctor.newInstance(parameters.map(m):*).asInstanceOf[T]
val img=填充[图像](m)
(请注意,此示例可以选择默认构造函数,因为它不会检查您想要执行的参数计数)下面是一个使用内置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"))
您可以将map转换为json,然后转换为case类。不过有点黑
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"
它们不能通过标准Java反射使用,但是您可以尝试解析字节……那么为什么不创建json数据,让lift json来完成其余的工作呢?这样,他就不必用scala的每个新版本自己更新它,也不必解析ScalaSignature字节。性能当然不太理想,但这对OP来说可能不是问题。我遗漏了什么吗?@Kim嘿,这是一个好办法-如果OP愿意为此创建JSON。或者,也可以使用
Map[String,String]
作为输入,更直接地重用lift-json……感谢您的提示。从Map到JSON字符串将JSON解析提升到case类似乎有很多不必要的序列化/反序列化处理。build2的类型参数与字段数成正比。我认为,这不是一个很好的方法。但是,如何在实例化类时避免参数类型不匹配
异常呢?这是我见过的对这个问题最简洁的回答。但是,它使用的API现在已被弃用。通过使用“ClassTag”而不是“ClassManifest”和“runtimeClass”而不是“erasure”,可以很容易地更新