问题描述
|
如果我有一个“ 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方法。