In scala, how to turn object's values into Map

2020-06-01 06:18发布

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.

标签: scala
3条回答
混吃等死
2楼-- · 2020-06-01 06:57

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")
查看更多
Evening l夕情丶
3楼-- · 2020-06-01 07:05

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?

查看更多
够拽才男人
4楼-- · 2020-06-01 07:22

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.

查看更多
登录 后发表回答