15

Let's say that I have this class

case class Test (id: Long, name: String)

and an instance of this class :

Test :
id -> 1
name -> toto

I would like to create a Map[String, String] as follow :

Map( "id" -> "1", "name" -> "toto")

My question is : Is there a way to turn this instance of Test into Map[String, String] ? I want to avoid to use a method as this one :

def createMap(instance: Test): Map[String, String] = {
    val map = new Map[String, String]
    map.put("id", instance.id.toString)
    map.put("name", instance.name)
    map
}

If there is no method to do so in Scala, is there a way to iterate over class properties ? Maybe I can create a generic function to do so :

def createMap(instance: T): Map[String, String] = {
   val map = new Map[String, String]
   //pseudocode 
   for  ((name, value) <- instance.getClassProperties.getValues) {
      case value.isInstanceOf[String] : map.push(name, value)
      case _ : map.push(name, value.toString)
    }
    map
}

Is that possible ? If you have good examples/links, I'm interested.

alexgindre
  • 243
  • 1
  • 4
  • 11
  • Answers to [this question](http://stackoverflow.com/questions/2224251/reflection-on-a-scala-case-class) may help. – incrop Oct 09 '12 at 10:23
  • Not that I don't like generic solutions, but for this problem I would use `Map("id" -> t.id.toString, "name" -> t.name)` where `t` is an instance of `Test`. – kiritsuku Oct 09 '12 at 12:09

3 Answers3

21

Yes it's possible. Since Scala 2.10 you can use reflection.

Assuming you have:

val test = Test(23423, "sdlkfjlsdk")

The following will get you what you want:

import reflect.runtime.universe._
import reflect.runtime.currentMirror

val r = currentMirror.reflect(test)
r.symbol.typeSignature.members.toStream
  .collect{case s : TermSymbol if !s.isMethod => r.reflectField(s)}
  .map(r => r.symbol.name.toString.trim -> r.get.toString)
  .toMap

To simply iterate over field values of case class use .productIterator on its instance.

Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169
  • Can this process reverse?? From Map to Object? If class full name is given – Jeff Lee Nov 28 '14 at 10:12
  • 1
    @JeffLee Yes. It's possible both with reflection and macros. Here's [an example using reflection of Scala 2.10](https://github.com/sorm/sorm/blob/master/src/main/scala/sorm/reflection/Reflection.scala#L50-L58). – Nikita Volkov Nov 28 '14 at 10:19
  • amazing. works perfectly well. just one update for 2.12, imports are `import scala.reflect.runtime.currentMirror` `import scala.reflect.runtime.universe._` – manishbelsare Jul 28 '18 at 01:09
  • @manishbelsare 2.12.6 just whines at me that there is no `typeSignature`, then it whines at me that there is no `members`... Does anything of it actually work in 2.12.6? – Andrey Tyukin Dec 12 '18 at 21:25
11

The topic you are dealing with is becoming incredibly recurring on StackOverFlow and the problem is not trivial if you want to have a typesafe implementation.

One way to solve the problem is using reflection (as suggested) but I personally prefer using the type system and implicits.

There is a well-known library, developed by an extremely smart guy, who let you perform advanced operations such as turn any case class into a typesafe heterogeneous list, or create heteregeneous maps, which can be used to implement "extensible records". The library is called Shapeless, and here one example:

object RecordExamples extends App {
  import shapeless._
  import HList._
  import Record._

  object author  extends Field[String]  { override def toString = "Author" }
  object title   extends Field[String]  { override def toString = "Title" }
  object id      extends Field[Int]     { override def toString = "ID" }
  object price   extends Field[Double]  { override def toString = "Price" }
  object inPrint extends Field[Boolean] { override def toString = "In print" }

  def printBook[B <: HList](b : B)(implicit tl : ToList[B, (Field[_], Any)]) = {
    b.toList foreach { case (field, value) => println(field+": "+value) }
    println
  }

  val book =
    (author -> "Benjamin Pierce") ::
    (title  -> "Types and Programming Languages") ::
    (id     ->  262162091) ::
    (price  ->  44.11) ::
    HNil

  printBook(book)

  // Read price field
  val currentPrice = book.get(price)  // Static type is Double
  println("Current price is "+currentPrice)
  println

  // Update price field, relying on static type of currentPrice
  val updated = book + (price -> (currentPrice+2.0))
  printBook(updated)

  // Add a new field
  val extended = updated + (inPrint -> true)
  printBook(extended)

  // Remove a field
  val noId = extended - id 
  printBook(noId)
}

Book behaves like a typesafe map which can indexed using objects as keys. If you are interested in knowing more, a good entry point might be this post:

Are HLists nothing more than a convoluted way of writing tuples?

Community
  • 1
  • 1
Edmondo
  • 19,559
  • 13
  • 62
  • 115
0

Starting Scala 2.13, case classes (as implementations of Product) are provided with a productElementNames method which returns an iterator over their field's names.

By zipping field names with field values obtained with productIterator we can generically obtain the associated Map[String, Any] and by mapping values with toString the associated Map[String, String]:

// case class Test(id: Long, name: String)
// val x = Test(1, "todo")
(x.productElementNames zip x.productIterator.map(_.toString)).toMap
// Map[String,String] = Map("id" -> "1", "name" -> "todo")
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190