В scala, как преобразовать значения объектов в Map [String, String]?

Скажем, что у меня этот класс

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

и экземпляр этого класса:

Test :
id -> 1
name -> toto

Я хотел бы создать карту [String, String] следующим образом:

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

Мой вопрос: есть ли способ превратить этот экземпляр Test в Map [String, String]? Я хочу избежать использования метода в качестве этого:

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
}

Если в Scala нет способа сделать это, существует ли способ перебора свойств класса? Возможно, я могу создать общую функцию для этого:

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
}

Это возможно? Если у вас есть хорошие примеры/ссылки, мне интересно.

Ответ 1

Да, это возможно. Поскольку Scala 2.10, вы можете использовать отражение.

Предполагая, что у вас есть:

val test = Test(23423, "sdlkfjlsdk")

Ниже вы получите то, что хотите:

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

Чтобы просто перебрать значения полей класса case, используйте .productIterator в своем экземпляре.

Ответ 2

Тема, с которой вы имеете дело, становится невероятно повторяющейся в StackOverFlow, и проблема не является тривиальной, если вы хотите иметь типичную реализацию.

Один из способов решения проблемы - использовать рефлексию (как было предложено), но я лично предпочитаю использовать систему типов и implicits.

Существует известная библиотека, разработанная чрезвычайно умным парнем, которая позволяет вам выполнять расширенные операции, такие как включение любого класса case в разнотипный список типов или создание гетерогенных карт, которые могут использоваться для реализации "расширяемых записей" ". Библиотека называется Shapeless, и здесь один пример:

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)
}

Книга ведет себя как карта типов, которая может индексироваться с использованием объектов в виде ключей. Если вам интересно узнать больше, хорошей точкой входа может быть этот пост:

Являются ли HLists не более чем запутанным способом написания кортежей?