Ответ 1
Это должно работать:
def getCCParams(cc: AnyRef) =
cc.getClass.getDeclaredFields.foldLeft(Map.empty[String, Any]) { (a, f) =>
f.setAccessible(true)
a + (f.getName -> f.get(cc))
}
Есть ли хороший способ конвертировать экземпляр case class
случая Scala, например
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"
Поэтому я ищу подобное решение, но для полей класса случая. Я мог бы предположить, что решение могло бы использовать отражение Java, но я бы не хотел писать что-то, что может сломаться в будущем выпуске Scala, если изменится базовая реализация классов case.
В настоящее время я работаю на сервере Scala и определяю протокол и все его сообщения и исключения, используя классы падежей, поскольку они являются такой красивой и лаконичной конструкцией для этого. Но затем мне нужно перевести их в карту Java для отправки через уровень обмена сообщениями для использования любой клиентской реализацией. Моя текущая реализация просто определяет перевод для каждого класса case отдельно, но было бы неплохо найти обобщенное решение.
Это должно работать:
def getCCParams(cc: AnyRef) =
cc.getClass.getDeclaredFields.foldLeft(Map.empty[String, Any]) { (a, f) =>
f.setAccessible(true)
a + (f.getName -> f.get(cc))
}
Поскольку классы case расширяют 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
}
Одним из преимуществ продукта является то, что вам не нужно вызывать setAccessible
в поле, чтобы прочитать его значение. Другим является то, что productIterator не использует отражение.
Обратите внимание, что этот пример работает с простыми классами классов, которые не распространяют другие классы и не объявляют поля вне конструктора.
Если кто-то ищет рекурсивную версию, это модификация решения @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)
Определите представление 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 = Map (b → second, a → first)
stringMapX = Карта (c → 26, b → bike, a → true)
stringMapY = Map (b → second, a → first)
Для вложенных классов case (таким образом, вложенные карты) проверьте другой ответ
Начиная с 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")
Решение с 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]]
Я не знаю о хорошем... но это, похоже, работает, по крайней мере, для этого очень простого примера. Это, вероятно, нуждается в некоторой работе, но может быть достаточно, чтобы вы начали? В основном он отфильтровывает все "известные" методы из класса 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