Reputation: 1748
I have a Scala reflection Symbol.
val symbol = scala.reflect.api.Symbol
how can I cast an object
val obj: Any
to the type of that Symbol ?
reason why I am asking is that I have a implicit method to convert Map[String, Any]
to a Scala case class, but in side I want to to cast on each field.
TODO part is the place I want to cast the type.
object MapStringAnyOps {
implicit class MapStringAnyOps(m: Map[String, _]) {
def toCaseClass[T: TypeTag : ClassTag]: T = {
val rm = runtimeMirror(classTag[T].runtimeClass.getClassLoader)
val destType = typeOf[T]
val classTest = destType.typeSymbol.asClass
val classMirror = rm.reflectClass(classTest)
val constructor = destType.decl(termNames.CONSTRUCTOR).asMethod
val constructorMirror = classMirror.reflectConstructor(constructor)
val constructorArgs = constructor.paramLists.flatten.map((param: Symbol) => {
val paramName = param.name.toString
if (param.typeSignature <:< typeOf[Option[Any]])
m.get(paramName) **// TODO in here, I can to cast this value of Any into the proper type of this field, but I only have Symbol so I can't simply use asInstanceOf[T]**
else
m.getOrElse(paramName, throw new IllegalArgumentException("Map is missing required parameter named " + paramName))
})
try {
constructorMirror(constructorArgs: _*).asInstanceOf[T]
} catch {
case e: java.lang.IllegalArgumentException =>
throw new java.lang.IllegalArgumentException(s"Error mapping to class constructor:\n Casting from row: ${constructorArgs.map(r => r.getClass.getName).mkString(",")}, to constructor: ${constructor.paramLists.flatten.map(r => r.typeSignature.toString).mkString(",")}", e)
}
}
}
}
Upvotes: 0
Views: 255
Reputation: 51703
You can't cast.
In order to cast .asInstanceOf[T]
you need to know T
at compile time.
But you know the symbol at runtime.
You can do something later (e.g. at runtime) based on the information you get earlier (e.g. at compile time) if you persist somehow the information. But you can't cast earlier (i.e. at compile time) based on the information you get later (i.e. at runtime).
But actually you don't need to cast. constructorMirror
accepts Any*
.
Just in case, you could cast if you knew the type at compile time, e.g. if you used compile-time reflection (i.e. macros) rather than runtime reflection
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait ToCaseClass[T] {
def apply(m: Map[String, _]): T
}
object ToCaseClass {
def apply[T: ToCaseClass]: ToCaseClass[T] = implicitly
implicit def mkToCaseClass[T]: ToCaseClass[T] = macro mkToCaseClassImpl[T]
def mkToCaseClassImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val T = weakTypeOf[T]
val constructor = T.decl(termNames.CONSTRUCTOR).asMethod
val constructorArgs = constructor.paramLists.flatten.map((param: Symbol) => {
val paramName = param.name.toString
val paramType = param.typeSignature
q"m($paramName).asInstanceOf[$paramType]"
})
q"""
new ToCaseClass[$T] {
override def apply(m: Map[String, _]): $T = new $T(..$constructorArgs)
}
"""
}
}
object MapStringAnyOps {
implicit class MapStringAnyOps(m: Map[String, _]) {
def toCaseClass[T: ToCaseClass]: T = ToCaseClass[T].apply(m)
}
}
case class A(i: Int, s: String)
import MapStringAnyOps._
Map("s" -> "a", "i" -> 1).toCaseClass[A] // A(1,a)
By the way, you can convert a Map
to a case class and back using libraries
import shapeless.{HList, LabelledGeneric}
import shapeless.ops.maps.FromMap
import shapeless.ops.product.ToMap
def fromMap[T]: PartiallyApplied[T] = new PartiallyApplied[T]
class PartiallyApplied[T] {
def apply[R <: HList](m: Map[Symbol, _])(implicit
gen: LabelledGeneric.Aux[T, R],
fromMap: FromMap[R]
): Option[T] = fromMap(m).map(gen.from)
}
fromMap[A].apply(Map("s" -> "a", "i" -> 1)
.map { case (k, v) => Symbol(k) -> v }) // converting Map[String, _] into Map[Symbol, _]
.get
// A(1,a)
ToMap[A].apply(A(1, "a"))
.map { case (k, v) => k.name -> v } // converting Map[Symbol, _] into Map[String, _]
// Map(s -> a, i -> 1)
In Shapeless there is a type class ToMap
converting a case class to a Map
but the type class FromMap
converts a Map
into HList
rather than case class, that's why we need fromMap
method.
How to use shapeless to convert generic Map[String, Any] to case class inside generic function?
Shapeless code to convert Map[String, Any] to case class cannot handle optional substructures
Converting Map[String,Any] to a case class using Shapeless
Scala macros for nested case classes to Map and other way around
Converting nested case classes to nested Maps using Shapeless
Convert Nested Case Classes to Nested Maps in Scala
Scala: convert map to case class
Upvotes: 1