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”,可以很容易地更新