如何将Map转换为Scala中的case类?

时间:2011-05-31 00:29:46

标签: class scala map case

如果我有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"))
}

但对于任何案例类的地图,有没有办法一次性写一次这个?

5 个答案:

答案 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方法。