Reputation: 1594
Is it possible to get the type name of a generic class in Scala? I know this isn't possible in Java with type erasure, but I was hoping that Scala would be a different case.
Currently I have to do something similar to this:
trait Model
case class User(id: String) extends Model
def fromMap[M<:Model : Manifest](data: Map[String, String], modelType: String) = {
modelType match {
case "user" => User(data.get("id").get)
}
}
val user = fromMap[User](Map("id" -> "id1"), "user")
Obviously it would be easier if I could work out "user" without having to have it passed in.
Upvotes: 16
Views: 12527
Reputation: 5618
For those of you coming to this question from a world with Scala 2.12.x or 2.13.x, ClassTag[T]
is the preferred way to do this.
import scala.reflect._
trait Model
case class User(id: String) extends Model
def fromMap[M <: Model : ClassTag](data: Map[String, String]) = {
val modelType = implicitly[ClassTag[M]].runtimeClass.getSimpleName
modelType match {
case "User" => User(data("id"))
}
}
val user = fromMap[User](Map("id" -> "id1"))
Upvotes: 12
Reputation: 1336
For Scala 2.11, following code works:
import scala.reflect.runtime.universe.{typeOf, TypeTag}
def name[T: TypeTag] = typeOf[T].typeSymbol.name.toString
def fullName[T: TypeTag] = typeOf[T].typeSymbol.fullName
Execution example:
scala> name[String]
res5: String = String
scala> fullName[String]
res6: String = java.lang.String
Ref: http://www.scala-lang.org/api/2.11.0/scala-reflect/index.html
Upvotes: 5
Reputation: 29528
Class name can be retrieved from a Manifest (or a ClassManifest) with manifest.erasure.getName
(erasure is the Class instance). For instance
def className[A : ClassManifest] = classManifest[A].erasure.getName
Edit : Seen Derek's answer, which makes this stuff with erasure.getName look rather stupid. I didn't consider toString. Still I hope what follows may be of interest
The difference between using ClassManifest
and Manifest
is that in the Manifest
for a generic class, the type parameter are guaranteed to be available, while they are best effort in ClassManifest
(compare signatures of typeParameters
in both classes). The drawback of this guarantee is that a Manifest
might not be implicitly available where a ClassManifest
would be.
Have you considered using typeclasses instead?
trait BuilderFromMap[A] {def build(data: Map[String, String]): A}
// or better, return Option[A], accounting for possible failure
object User {
implicit val Builder extends BuilderFromMap[User] {...}
}
def fromMap[A](data: Map[String, String])(implicit builder: BuilderFromMap[A])
= builder.build(data)
This way, calls to fromMap
will compile only if a Builder
is available for this particular class, rather than fail with a MatchError
.
Upvotes: 12
Reputation: 2717
This should work (but I had to edit your code in order to guess what you wanted):
trait Model
case class User(id: String) extends Model
object Main extends App {
def fromMap[M <: Model](data: Map[String, String])(implicit m: reflect.Manifest[M]): Model = {
m.toString match {
case "User" => User(data.get("id").get)
}
}
val user = fromMap[User](Map("id" -> "id1"))
println(user)
}
But based on the trouble I had with it, I'm sure someone could do something better :)
Upvotes: 2