如何在Scala中将Map转换为案例类?

| 如果我有一个“ 0”,是否有办法将其一般地转换成“ 1”? 我可以写一个助手:
object Image{
  def fromMap(params:Map[String,String]) = Image(url=params(\"url\"), title=params(\"title\"))
}
但是有没有一种方法可以通用地为映射到任何case类的代码编写一次?     
已邀请:
        首先,如果您只是想缩短代码,可以采取一些安全的替代方法。伴随对象可以被视为一个函数,因此您可以使用如下形式:
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)
(请注意,此示例可以选择默认构造函数,因为它不会检查您想要执行的参数计数)     
        这是使用内置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\"))
    
        不是您问题的完整答案,而是一个开始…… 可以做到,但是可能会比您想象的要棘手。每个生成的Scala类都用Java注释ѭ10进行注释,可以对Java注释的ѭ11成员进行解析,以提供所需的元数据(包括参数名称)。但是,此签名的格式不是API,因此您需要自己解析(并且可能会在每个新的主要Scala版本中更改解析方式)。 也许最好的起点是lift-json库,它具有根据JSON数据创建案例类实例的功能。 更新:我认为lift-json实际上使用Paranamer来做到这一点,因此可能无法解析
ScalaSignature
的字节……这也使该技术也适用于非Scala类。 更新2:请改用莫里兹的答案,谁比我更了解我。     
        您可以将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 build中使用此依赖项:
 \"io.spray\"          %%   \"spray-json\"     %   \"1.3.3\"
    
        无法做到这一点,因为您需要获取伴随对象的apply方法的参数名,而它们根本无法通过反射获得。如果您有很多这样的案例类,则可以解析它们的声明并生成fromMap方法。     

要回复问题请先登录注册