Ben Smith
Ben Smith

Reputation: 1594

Get the specific simple name of a generic type in Scala

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

Answers (4)

Jake Greene
Jake Greene

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

skwon
skwon

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

Didier Dupont
Didier Dupont

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

Derek Wyatt
Derek Wyatt

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

Related Questions