1

I'm trying to convert a Scala Map[String, Any] to a case class using Scala reflection (Scala 2.11) as follows -

val m = Map("name" -> "ABC", "age" -> 7, "gender" -> "male")
case class someCC(name: String, age: Int, gender: String)

import scala.reflect.ClassTag

   def createCaseClass[T](someMap : Map[String, Any])(implicit someClassTag : ClassTag[T]) = {

    val ctor = someClassTag.runtimeClass.getConstructors.head
    val args = someClassTag.runtimeClass.getDeclaredFields.map(x => someMap(x.getName))

       ctor.newInstance(args: _*).asInstanceOf[T]
  }

this unfortunately results in a compile error -

Name: Unknown Error
Message: <console>:106: error: type mismatch;
 found   : Array[Any]
 required: Array[_ <: Object]
Note: Any >: Object, but class Array is invariant in type T.
You may wish to investigate a wildcard type such as `_ >: Object`. (SLS 3.2.10)
              ctor.newInstance(args: _*).asInstanceOf[T]
                           ^

I'm fairly new to using ClassTags and I understand that this error is primarily because java.lang.Object is a subset of Any and Any could include non-java objects.

When I tried to replace Any with AnyRef (which corresponds to java.lang.Object in JRE), the function call results in a type mismatch error.

import scala.reflect.ClassTag

   def createCaseClass[T](someMap : Map[String, AnyRef])(implicit someClassTag : ClassTag[T]) = {

    val ctor = someClassTag.runtimeClass.getConstructors.head
    val args = someClassTag.runtimeClass.getDeclaredFields.map(x => someMap(x.getName))

       ctor.newInstance(args: _*).asInstanceOf[T]
  }

val someCC = createCaseClass[someCC](m)

Name: Unknown Error
Message: <console>:106: error: type mismatch;
 found   : scala.collection.immutable.Map[String,Any]
 required: Map[String,AnyRef]
       val someCC = createCaseClass[someCC](m)

What's the best way to resolve this error? Suggestions appreciated. Thanks!

Update 1 - Updating this to implicitly cast an Any to AnyRef leads to an error 'java.util.NoSuchElementException' on function call.

import scala.reflect.ClassTag
import scala.reflect.runtime.universe._

   def createMyClass[T](someMap : Map[String, Any])(implicit someClassTag : ClassTag[T]) = {

       val ctor = someClassTag.runtimeClass.getConstructors.head

       val args = someClassTag.runtimeClass.getDeclaredFields.map(x => someMap(x.getName))

       ctor.newInstance(args.asInstanceOf[Seq[AnyRef]]: _*).asInstanceOf[T]

}

val m = Map("name" -> "ABC", "age" -> 7, "gender" -> "male")
case class someCC(name: String, age: Int, gender: String)

createMyClass[someCC](m)

Name: java.util.NoSuchElementException
Message: key not found: $outer
StackTrace:   at scala.collection.MapLike$class.default(MapLike.scala:228)
  at scala.collection.AbstractMap.default(Map.scala:59)
  at scala.collection.MapLike$class.apply(MapLike.scala:141)
  at scala.collection.AbstractMap.apply(Map.scala:59)
  at $$$e75186ae1b35495ffea8e318378149a$$$$anonfun$1.apply(<console>:135)
  at $$$e75186ae1b35495ffea8e318378149a$$$$anonfun$1.apply(<console>:135)
  at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
  at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
  at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
  at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:186)
  at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
  at scala.collection.mutable.ArrayOps$ofRef.map(ArrayOps.scala:186)
  at createMyClass(<console>:135)

What am I doing wrong here?

Yash
  • 1,080
  • 2
  • 13
  • 24
  • If you do it the Java way, you need to pass in the outer class instance in the parameter `$outer`. – HTNW Aug 14 '17 at 22:07

1 Answers1

3

Use a cast:

ctor.newInstance(args.asInstanceOf[Seq[AnyRef]]: _*).asInstanceOf[T]

Note that you map every field of a case class to a constructor param. This is incorrect, because a case class can have fields that aren't in the constructor, and your code will break.

A better idea is to use Scala reflection:

import reflect.runtime.universe._

def mkClassInstance[T: TypeTag](args: Map[String, Any]): T = {
  val rMirror = runtimeMirror(getClass.getClassLoader)
  val cMirror = rMirror.reflectClass(typeOf[T].typeSymbol.asClass)
  // The primary constructor is the first one
  val ctor = typeOf[T].decl(termNames.CONSTRUCTOR).asTerm.alternatives.head.asMethod
  val argList = ctor.paramLists.flatten.map(param => args(param.name.toString))
  cMirror.reflectConstructor(ctor)(argList: _*).asInstanceOf[T]
}
def mkInnerClassInstance[T: TypeTag](outer: Any)(args: Map[String, Any]): T = {
  val rMirror = runtimeMirror(getClass.getClassLoader)
  val cMirror = rMirror.reflect(outer).reflectClass(typeOf[T].typeSymbol.asClass)
  // The primary constructor is the first one
  val ctor = typeOf[T].decl(termNames.CONSTRUCTOR).asTerm.alternatives.head.asMethod
  val argList = ctor.paramLists.flatten.map(param => args(param.name.toString))
  cMirror.reflectConstructor(ctor)(argList: _*).asInstanceOf[T]
}
HTNW
  • 27,182
  • 1
  • 32
  • 60
  • Thanks HTNW. But this results in an error - Name: scala.ScalaReflectionException Message: class someCC is an inner class, use reflectClass on an InstanceMirror to obtain its ClassMirror StackTrace: at scala.reflect.runtime.JavaMirrors$JavaMirror.scala$reflect$runtime$JavaMirrors$JavaMirror$$abort(JavaMirrors.scala:115) at scala.reflect.runtime.JavaMirrors$JavaMirror.ErrorInnerClass(JavaMirrors.scala:117) at scala.reflect.runtime.JavaMirrors$JavaMirror.reflectClass(JavaMirrors.scala:183) at scala.reflect.runtime.JavaMirrors$JavaMirror.reflectClass(JavaMirrors.scala – Yash Aug 14 '17 at 21:41
  • 1
    Yep, this needs a bit of tweaking for inner classes; give me a moment. – HTNW Aug 14 '17 at 21:47
  • Thanks. Could you elaborate on How would I use outer in this specific scenario? – Yash Aug 14 '17 at 22:48
  • `mkInnerClassInstance[outer.SomeClass](outer)(Map(...))` – HTNW Aug 14 '17 at 22:49
  • `val a = someCC("ABC", 0, "male") mkInnerClassInstance[someCC](a)(m)` Is this incorrect? Could you please provide an example of the function call as I'm having a hard time wrapping my head around it? – Yash Aug 14 '17 at 23:19
  • `case class TestO(i: Int) { case class TestI(s: String) }; val outer = mkClassInstance[TestO](Map("i" -> 0)) /* outer class */; val inner = mkInnerClassInstance[outer.TestI](outer)(Map("s" -> "a")) /* inner class in object outer */;` – HTNW Aug 14 '17 at 23:23
  • Is your class defined like `object obj { class clazz }`? If so, then it's an inner class where the outer object is `obj`, so your call would be `object obj { class clazz; mkInnerClassInstance[clazz](this)(Map(...)) }`. – HTNW Aug 14 '17 at 23:26
  • the case classes are not defined under other class or object. If that's the case, what is the best way to do this? – Yash Aug 14 '17 at 23:52
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/151930/discussion-between-htnw-and-yash). – HTNW Aug 14 '17 at 23:56