How can I transform a Map to a case class in Scala?
First off, there are some safe alternatives you could do if you just want to shorten your code. The companion object can be treated as a function so you could use something like this:
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")
This will return an option containing the result. Alternatively you could use the ApplicativeBuilder
s in Scalaz which internally do almost the same but with a nicer syntax:
import scalaz._, Scalaz._
(m.get("url") |@| m.get("title"))(Image)
If you really need to do this via reflection then the easiest way would be to use Paranamer (as the Lift-Framework does). Paranamer can restore the parameter names by inspecting the bytecode so there is a performance hit and it will not work in all environments due to classloader issues (the REPL for example). If you restrict yourself to classes with only String
constructor parameters then you could do it like this:
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)
(Note that this example can pick a default constructor as it does not check for the parameter count which you would want to do)
Here's a solution using builtin scala/java reflection:
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]
}
To use it:
val image = createCaseClass[Image](Map("url" -> "xxx", "title" -> "yyy"))