Вопрос

Есть ли хороший способ, которым я могу преобразовать Scala case class пример, например

case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")

в какое-либо отображение, например

getCCParams(x) returns "param1" -> "hello", "param2" -> "world"

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

def getCCName(caseobj: Product) = caseobj.productPrefix 
getCCName(x) returns "MyClass"

Итак, я ищу аналогичное решение, но для полей класса case.Я бы предположил, что решение, возможно, должно использовать Java reflection, но мне бы не хотелось писать что-то, что может сломаться в будущей версии Scala, если базовая реализация классов case изменится.

В настоящее время я работаю на сервере Scala и определяю протокол и все его сообщения и исключения, используя классы case, поскольку они представляют собой такую красивую, лаконичную конструкцию для этого.Но затем мне нужно перевести их в Java map для отправки через уровень обмена сообщениями для использования любой клиентской реализацией.Моя текущая реализация просто определяет перевод для каждого класса case отдельно, но было бы неплохо найти обобщенное решение.

Это было полезно?

Решение

Это должно работать:

def getCCParams(cc: AnyRef) =
  (Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
    f.setAccessible(true)
    a + (f.getName -> f.get(cc))
  }

Другие советы

Поскольку классы дел расширяются, Product можно просто использовать . productIterator для получения значений полей:

def getCCParams(cc: Product) = cc.getClass.getDeclaredFields.map( _.getName ) // all field names
                .zip( cc.productIterator.to ).toMap // zipped with all values

Или альтернативно:

def getCCParams(cc: Product) = {          
      val values = cc.productIterator
      cc.getClass.getDeclaredFields.map( _.getName -> values.next ).toMap
}

Одним из преимуществ Product является то, что вам не нужно вызывать setAccessible для поля, чтобы прочитать его значение. Другое - это то, что productIterator не использует отражение.

Обратите внимание, что этот пример работает с простыми классами case, которые не расширяют другие классы и не объявляют поля вне конструктора.

Если кто-нибудь ищет рекурсивную версию, вот модификация решения @ Andrejs:

def getCCParams(cc: Product): Map[String, Any] = {
  val values = cc.productIterator
  cc.getClass.getDeclaredFields.map {
    _.getName -> (values.next() match {
      case p: Product if p.productArity > 0 => getCCParams(p)
      case x => x
    })
  }.toMap
}

Он также расширяет вложенные case-классы в карты на любом уровне вложенности.

Вот простой вариант, если вам не нужно делать его универсальной функцией:

case class Person(name:String, age:Int)

def personToMap(person: Person): Map[String, Any] = {
  val fieldNames = person.getClass.getDeclaredFields.map(_.getName)
  val vals = Person.unapply(person).get.productIterator.toSeq
  fieldNames.zip(vals).toMap
}

scala> println(personToMap(Person("Tom", 50)))
res02: scala.collection.immutable.Map[String,Any] = Map(name -> Tom, age -> 50)

Вы могли бы использовать бесформенный.

Пусть

case class X(a: Boolean, b: String,c:Int)
case class Y(a: String, b: String)

Определите помеченное общее представление

import shapeless._
import shapeless.ops.product._
import shapeless.syntax.std.product._
object X {
  implicit val lgenX = LabelledGeneric[X]
}
object Y {
  implicit val lgenY = LabelledGeneric[Y]
}

Определите два класса типов для предоставления методов toMap

object ToMapImplicits {

  implicit class ToMapOps[A <: Product](val a: A)
    extends AnyVal {
    def mkMapAny(implicit toMap: ToMap.Aux[A, Symbol, Any]): Map[String, Any] =
      a.toMap[Symbol, Any]
        .map { case (k: Symbol, v) => k.name -> v }
  }

  implicit class ToMapOps2[A <: Product](val a: A)
    extends AnyVal {
    def mkMapString(implicit toMap: ToMap.Aux[A, Symbol, Any]): Map[String, String] =
      a.toMap[Symbol, Any]
        .map { case (k: Symbol, v) => k.name -> v.toString }
  }
}

Тогда вы можете использовать его следующим образом.

object Run  extends App {
  import ToMapImplicits._
  val x: X = X(true, "bike",26)
  val y: Y = Y("first", "second")
  val anyMapX: Map[String, Any] = x.mkMapAny
  val anyMapY: Map[String, Any] = y.mkMapAny
  println("anyMapX = " + anyMapX)
  println("anyMapY = " + anyMapY)

  val stringMapX: Map[String, String] = x.mkMapString
  val stringMapY: Map[String, String] = y.mkMapString
  println("anyMapX = " + anyMapX)
  println("anyMapY = " + anyMapY)
}

который печатает

anyMapX = Карта (c -> 26, b -> велосипед, a -> истина)

anyMapY = Карта (b -> вторая, a -> первая)

stringMapX = Карта(c -> 26, b -> велосипед, a -> истина)

Stringmap = Карта(b -> вторая, a -> первая)

Для вложенных классов case (таким образом, вложенных карт) проверьте другой ответ

Решение с ProductCompletion из пакета интерпретатора:

import tools.nsc.interpreter.ProductCompletion

def getCCParams(cc: Product) = {
  val pc = new ProductCompletion(cc)
  pc.caseNames.zip(pc.caseFields).toMap
}

Если вы используете Json4s, вы можете сделать следующее:

import org.json4s.{Extraction, _}

case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")

Extraction.decompose(x)(DefaultFormats).values.asInstanceOf[Map[String,String]]

Запуск Scala 2.13 , case case es (как реализации Product ) поставляются с productElementNames , который возвращает итератор над именами их полей.

Путем архивирования имен полей со значениями полей, полученными с помощью productIterator мы можем в общем случае получить связанную карту :

// case class MyClass(param1: String, param2: String)
// val x = MyClass("hello", "world")
(x.productElementNames zip x.productIterator).toMap
// Map[String,Any] = Map("param1" -> "hello", "param2" -> "world")

Я не знаю о хорошем ... но это, кажется, работает, по крайней мере, для этого очень простого примера. Это, вероятно, требует некоторой работы, но может быть достаточно, чтобы вы начали? В основном это отфильтровывает все "известные" методы из класса case (или любого другого класса: /)

object CaseMappingTest {
  case class MyCase(a: String, b: Int)

  def caseClassToMap(obj: AnyRef) = {
    val c = obj.getClass
    val predefined = List("$tag", "productArity", "productPrefix", "hashCode",
                          "toString")
    val casemethods = c.getMethods.toList.filter{
      n =>
        (n.getParameterTypes.size == 0) &&
        (n.getDeclaringClass == c) &&
        (! predefined.exists(_ == n.getName))

    }
    val values = casemethods.map(_.invoke(obj, null))
    casemethods.map(_.getName).zip(values).foldLeft(Map[String, Any]())(_+_)
  }

  def main(args: Array[String]) {
    println(caseClassToMap(MyCase("foo", 1)))
    // prints: Map(a -> foo, b -> 1)
  }
}
commons.mapper.Mappers.Mappers.beanToMap(caseClassBean)

Подробности: https://github.com/hank-whu/common4s

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top