Klasa Case do mapowania w Scali
Czy Jest jakiś dobry sposób na konwersję instancji Scali case class
, np.
case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")
Do jakiegoś odwzorowania, np.
getCCParams(x) returns "param1" -> "hello", "param2" -> "world"
Który działa dla każdej klasy przypadków, a nie tylko predefiniowanych. Odkryłem, że można wyciągnąć nazwę klasy case, pisząc metodę, która przesłuchuje podstawową klasę produktu, np.
def getCCName(caseobj: Product) = caseobj.productPrefix
getCCName(x) returns "MyClass"
Więc szukam podobnego rozwiązania, ale dla pól case class. Wyobrażam sobie, że rozwiązaniem może być Java reflection, ale nie chciałbym napisz coś, co może się zepsuć w przyszłym wydaniu Scali, jeśli podstawowa implementacja klas przypadków ulegnie zmianie.
Obecnie pracuję nad serwerem Scala i definiuję protokół oraz wszystkie jego komunikaty i wyjątki używając klas case, ponieważ są one tak piękną, zwięzłą konstrukcją do tego celu. Ale potem muszę przetłumaczyć je na mapę Javy, aby wysłać warstwę wiadomości dla dowolnej implementacji klienta do użycia. Moja obecna implementacja definiuje tylko tłumaczenie dla każdego przypadku klasy osobno, ale miło byłoby znaleźć ogólne rozwiązanie.
11 answers
To powinno zadziałać:
def getCCParams(cc: AnyRef) =
cc.getClass.getDeclaredFields.foldLeft(Map.empty[String, Any]) { (a, f) =>
f.setAccessible(true)
a + (f.getName -> f.get(cc))
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-16 17:45:23
Ponieważ klasy case rozszerzają Produkt można po prostu użyć .productIterator
, aby uzyskać wartości pól:
def getCCParams(cc: Product) = cc.getClass.getDeclaredFields.map( _.getName ) // all field names
.zip( cc.productIterator.to ).toMap // zipped with all values
Lub alternatywnie:
def getCCParams(cc: Product) = {
val values = cc.productIterator
cc.getClass.getDeclaredFields.map( _.getName -> values.next ).toMap
}
Jedną z zalet produktu jest to, że nie trzeba wywoływać setAccessible
na polu, aby odczytać jego wartość. Innym jest to, że productIterator nie używa odbicia.
Zauważ, że ten przykład działa z prostymi klasami przypadków, które nie rozszerzają innych klas i nie deklarują pól poza konstruktorem.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-04 22:00:12
Początek Scala 2.13
, case class
es (jako implementacje Product
) są dostarczane z metodą productElementNames , która zwraca iterator nad nazwami ich pól.
Poprzez zsynchronizowanie nazw pól z wartościami pól uzyskanymi za pomocą productIterator możemy ogólnie uzyskać powiązane 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")
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-18 22:27:05
Jeśli ktoś szuka wersji rekurencyjnej, oto modyfikacja rozwiązania @ 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
}
Rozszerza również zagnieżdżone klasy przypadków na mapy na dowolnym poziomie zagnieżdżania.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-18 08:23:18
Oto prosta odmiana, jeśli nie zależy ci na uczynieniu z niej funkcji ogólnej:
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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-20 22:19:48
Rozwiązanie z ProductCompletion
z pakietu interpretera:
import tools.nsc.interpreter.ProductCompletion
def getCCParams(cc: Product) = {
val pc = new ProductCompletion(cc)
pc.caseNames.zip(pc.caseFields).toMap
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-06 13:09:47
Przydałby Ci się bezkształtny.
Let
case class X(a: Boolean, b: String,c:Int)
case class Y(a: String, b: String)
Define a LabelledGeneric representation
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]
}
Zdefiniuj dwa typy, aby zapewnić metody 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 }
}
}
Wtedy możesz go użyć w ten sposób.
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)
}
Które drukuje
AnyMapX = Map (c -> 26, b -> bike, a -> true)
AnyMapY = Map (b - > second, a - > first)
StringMapX = Map (c -> 26, b -> bike, a -> true)
StringMapY = Map (b - > second, a - > first)
Dla zagnieżdżonych klasy przypadków, (tak zagnieżdżone mapy) sprawdź kolejna odpowiedź
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55
Jeśli przypadkiem używasz Json4s, możesz wykonać następujące czynności:
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]]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-08 12:51:00
Nie wiem jak miło... ale to wydaje się działać, przynajmniej w tym bardzo podstawowym przykładzie. Prawdopodobnie wymaga trochę pracy, ale może wystarczyć, aby zacząć? Zasadniczo odfiltrowuje wszystkie" znane " metody z klasy case (lub dowolnej innej klasy :/ )
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)
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-04 12:53:36
commons.mapper.Mappers.Mappers.beanToMap(caseClassBean)
Szczegóły: https://github.com/hank-whu/common4s
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-12 10:17:10
Z wykorzystaniem Java reflection, ale bez zmiany poziomu dostępu. Konwertuje Product
i klasę case na Map[String, String]
:
def productToMap[T <: Product](obj: T, prefix: String): Map[String, String] = {
val clazz = obj.getClass
val fields = clazz.getDeclaredFields.map(_.getName).toSet
val methods = clazz.getDeclaredMethods.filter(method => fields.contains(method.getName))
methods.foldLeft(Map[String, String]()) { case (acc, method) =>
val value = method.invoke(obj).toString
val key = if (prefix.isEmpty) method.getName else s"${prefix}_${method.getName}"
acc + (key -> value)
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-03 09:36:38