Класс Case для отображения в Scala

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

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

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

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

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

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

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

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

68 голосов | спросил Will 4 PM00000010000003931 2009, 13:40:39

10 ответов


0

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

def getCCParams(cc: AnyRef) =
  (Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
    f.setAccessible(true)
    a + (f.getName -> f.get(cc))
  }
ответил Walter Chang 4 PM00000050000003431 2009, 17:48:34
0

Поскольку классы дел расширяются, 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 5 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 05 Sep 2013 00:56:50 +0400 2013, 00:56:50
0

Если кто-нибудь ищет рекурсивную версию, вот модификация решения @ 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-классы в карты на любом уровне вложенности.

ответил Piotr Krzemiński 17 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowWed, 17 Sep 2014 18:59:56 +0400 2014, 18:59:56
0

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

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)
ответил ShawnFumo 21 FebruaryEurope/MoscowbFri, 21 Feb 2014 02:19:48 +0400000000amFri, 21 Feb 2014 02:19:48 +040014 2014, 02:19:48
0

Решение с помощью ProductCompletion из пакета интерпретатора:

import tools.nsc.interpreter.ProductCompletion

def getCCParams(cc: Product) = {
  val pc = new ProductCompletion(cc)
  pc.caseNames.zip(pc.caseFields).toMap
}
ответил Stefan Endrullis 6 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowTue, 06 Sep 2011 17:09:47 +0400 2011, 17:09:47
0

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

Пусть

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

Определите представление LabelledGeneric

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 = Map (c -> 26, b -> bike, a -> true)

     

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

     

stringMapX = Map (c -> 26, b -> bike, a -> true)

     

stringMapY = Map (b -> second, a -> first)

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

ответил Harry Laou 18 PM000000110000001631 2016, 23:15:16
0

Если вы используете 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]]
ответил Barak BN 8 Maypm17 2017, 15:51:00
0

Я не знаю о хорошем ... но, похоже, это работает, по крайней мере, для этого очень простого примера. Это, вероятно, требует некоторой работы, но может быть достаточно, чтобы вы начали? По сути, он отфильтровывает все «известные» методы из класса 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)
  }
}
ответил André Laszlo 4 PM00000040000003431 2009, 16:29:34
0
commons.mapper.Mappers.Mappers.beanToMap(caseClassBean)
Подробнее о >
ответил Kai Han 12 PM00000010000001031 2015, 13:17:10
0

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

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

// 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")
ответил Xavier Guihot 19 WedEurope/Moscow2018-12-19T01:27:05+03:00Europe/Moscow12bEurope/MoscowWed, 19 Dec 2018 01:27:05 +0300 2018, 01:27:05

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132