Fallklasse zur Karte in Scala
-
22-07-2019 - |
Frage
Gibt es eine nette Art, wie ich eine Scala case class
Instanz umwandeln kann, z.
case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")
in eine Abbildung von einer Art, z.
getCCParams(x) returns "param1" -> "hello", "param2" -> "world"
, die für jeden Fall Klasse funktioniert, nicht nur vordefinierte diejenigen. Ich habe festgestellt Sie den Fall Klassennamen aus, indem er ein Verfahren ziehen können, die die zugrunde liegende Produktklasse abfragt, z.
def getCCName(caseobj: Product) = caseobj.productPrefix
getCCName(x) returns "MyClass"
Also habe ich mich für eine ähnliche Lösung, aber für den Fall Klassenfelder. Ich könnte mir vorstellen, eine Lösung könnte Java Reflexion verwenden, aber ich würde es hassen, etwas zu schreiben, die in einer zukünftigen Version von Scala, wenn die zugrunde liegende Implementierung von Fallklassen Änderungen brechen könnten.
Zur Zeit arbeite ich an einem Scala-Server und definiere das Protokoll und alle seine Nachrichten und Ausnahmen anhand von Fall Klassen, da sie so ein schönes, prägnantes Konstrukt dafür ist. Aber ich brauche sie dann in eine Java-Karte übersetzen über die Messaging-Schicht zu senden für jede Client-Implementierung zu verwenden. Meine aktuelle Implementierung definiert nur eine Übersetzung für jeweils Klasse getrennt, aber es wäre schön, eine verallgemeinerte Lösung zu finden.
Lösung
Dies sollte funktionieren:
def getCCParams(cc: AnyRef) =
(Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
f.setAccessible(true)
a + (f.getName -> f.get(cc))
}
Andere Tipps
Weil Fall Klassen erweitern Produkt man einfach .productIterator
Feld bekommen können Werte:
def getCCParams(cc: Product) = cc.getClass.getDeclaredFields.map( _.getName ) // all field names
.zip( cc.productIterator.to ).toMap // zipped with all values
Oder alternativ:
def getCCParams(cc: Product) = {
val values = cc.productIterator
cc.getClass.getDeclaredFields.map( _.getName -> values.next ).toMap
}
Ein Vorteil Produkt ist, dass Sie nicht brauchen, um rufen setAccessible
auf dem Feld seinen Wert zu lesen. Ein weiterer Grund ist, dass productIterator nicht Reflexion nicht verwendet.
Beachten Sie, dass dieses Beispiel mit einfachen Fall Klassen arbeitet, die nicht ausdrücklich erklären, andere Klassen erweitern und nicht Felder außerhalb des Konstruktor.
Wenn jemand für eine rekursive Version sieht, hier ist die Modifikation von @ Andrejs Lösung:
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
}
Es erweitert auch die verschachtelten Fall-Klassen in Karten auf jeder Ebene der Verschachtelung.
Hier ist eine einfache Variante, wenn Sie nicht über die es eine generische Funktion ist es egal:
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)
Sie können formlos verwenden.
Let
case class X(a: Boolean, b: String,c:Int)
case class Y(a: String, b: String)
Definieren eine LabelledGeneric Darstellung
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]
}
Definieren Sie zwei typeclasses die toMap Methoden zur Verfügung zu stellen
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 }
}
}
Dann können Sie es wie folgt verwendet werden.
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)
}
Ein Faxbericht wird gedruckt
anyMapX = Karte (c -> 26, b -> Fahrrad, a -> true)
anyMapY = Karte (b -> zweiter, a -> zuerst)
stringMapX = Karte (c -> 26, b -> Fahrrad, a -> true)
stringMapY = Karte (b -> zweiter, a -> zuerst)
Für verschachtelten Fall Klassen (also verschachtelte Karten) überprüfen eine andere Antwort
Lösung mit ProductCompletion
von Interpreter-Paket:
import tools.nsc.interpreter.ProductCompletion
def getCCParams(cc: Product) = {
val pc = new ProductCompletion(cc)
pc.caseNames.zip(pc.caseFields).toMap
}
Wenn Sie zufällig zu Json4s verwenden, könnten Sie wie folgt vor:
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]]
Starten Scala 2.13
, case class
es (als Implementierungen von Product
) mit einem productElementNames Methode, die einen Iterator zurückgibt über Namen ihres Feldes.
Mit dem Zippen Feldnamen mit Feldwerten, die mit productIterator können wir allgemein die zugehörige Map
erhalten:
// 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")
Ich weiß nicht, über schön ... aber das scheint, zumindest für dieses sehr sehr einfaches Beispiel zu arbeiten. Es braucht wohl etwas Arbeit, aber könnte ausreichen, um Sie anfangen sollen? Grundsätzlich filtert sie heraus alle „bekannte“ Methoden aus einer Fallklasse (oder jede andere Klasse: /)
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)
Details: https://github.com/hank-whu/common4s