Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In scala, how to turn object's values into Map[String, String]?

Tags:

scala

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.

like image 633
alexgindre Avatar asked Oct 09 '12 09:10

alexgindre


People also ask

What is map string string in scala?

A map represents an mapping from keys to values. The Scala Map type has two type parameters, for the key type and for the value type. So a Map[String, Int] maps strings to integers, while a Map[Int, Set[String]] maps integers to sets of strings. Scala provides both immutable and mutable maps.

How do I convert a list to map in scala?

To convert a list into a map in Scala, we use the toMap method. We must remember that a map contains a pair of values, i.e., key-value pair, whereas a list contains only single values. So we have two ways to do so: Using the zipWithIndex method to add indices as the keys to the list.

How do you add a string value to a string map?

To insert the data in the map insert() function in the map is used. It is used to insert elements with a particular key in the map container.


2 Answers

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.

like image 181
Nikita Volkov Avatar answered Oct 08 '22 15:10

Nikita Volkov


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?

like image 28
Edmondo1984 Avatar answered Oct 08 '22 15:10

Edmondo1984