Gibt es eine gute Möglichkeit, eine Scala- case class
Instanz zu konvertieren , z
case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")
in eine Art Mapping, z
getCCParams(x) returns "param1" -> "hello", "param2" -> "world"
Was für jede Fallklasse funktioniert, nicht nur für vordefinierte. Ich habe festgestellt, dass Sie den Namen der Fallklasse herausziehen können, indem Sie eine Methode schreiben, die die zugrunde liegende Produktklasse abfragt, z
def getCCName(caseobj: Product) = caseobj.productPrefix
getCCName(x) returns "MyClass"
Ich suche also nach einer ähnlichen Lösung, aber nach den Feldern für Fallklassen. Ich würde mir vorstellen, dass eine Lösung möglicherweise Java-Reflektion verwenden muss, aber ich würde es hassen, etwas zu schreiben, das in einer zukünftigen Version von Scala möglicherweise kaputt geht, wenn sich die zugrunde liegende Implementierung von Fallklassen ändert.
Derzeit arbeite ich an einem Scala-Server und definiere das Protokoll und alle seine Nachrichten und Ausnahmen mithilfe von Fallklassen, da diese ein so schönes, prägnantes Konstrukt dafür sind. Aber ich muss sie dann in eine Java-Map übersetzen, um sie über die Messaging-Schicht zu senden, damit jede Client-Implementierung sie verwenden kann. Meine aktuelle Implementierung definiert nur eine Übersetzung für jede Fallklasse separat, aber es wäre schön, eine verallgemeinerte Lösung zu finden.
quelle
Antworten:
Das sollte funktionieren:
def getCCParams(cc: AnyRef) = cc.getClass.getDeclaredFields.foldLeft(Map.empty[String, Any]) { (a, f) => f.setAccessible(true) a + (f.getName -> f.get(cc)) }
quelle
isAccessible
, dagetMethod
nur öffentliche Methoden zurückgegeben werden. Auchaccessor != null
ist der falsche Test, da agetMethod
auslöst,NoSuchMethodException
wenn die Methode nicht gefunden wird.case class Person(name: String, surname: String) val person = new Person("Daniele", "DalleMule") val personAsMap = person.getClass.getDeclaredFields.foldLeft(Map[String, Any]())((map, field) => { field.setAccessible(true) map + (field.getName -> field.get(person)) } )
Da Fallklassen das Produkt erweitern, können Sie einfach
.productIterator
Feldwerte abrufen: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 von Product ist, dass Sie nicht anrufen müssen
setAccessible
das Feld , um seinen Wert zu lesen. Ein weiterer Grund ist, dass productIterator keine Reflexion verwendet.Beachten Sie, dass dieses Beispiel mit einfachen Fallklassen funktioniert, die andere Klassen nicht erweitern und keine Felder außerhalb des Konstruktors deklarieren.
quelle
getDeclaredFields
Spezifikation heißt es: "Die zurückgegebenen Elemente im Array sind nicht sortiert und befinden sich nicht in einer bestimmten Reihenfolge." Wie kommt es, dass die Felder in der richtigen Reihenfolge zurückgegeben werden?Ab dem Start
Scala 2.13
werdencase class
es (als Implementierungen vonProduct
) mit einem productElementNames versehen Methode , die einen Iterator über die Namen ihrer Felder zurückgibt.Durch Zippen von Feldnamen mit Feldwerten, die mit productIterator erhalten wurden , können wir generisch Folgendes erhalten
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")
quelle
Wenn jemand nach einer rekursiven Version sucht, ist hier die Modifikation der Lösung von @ 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 }
Außerdem werden die verschachtelten Fallklassen auf jeder Verschachtelungsebene zu Karten erweitert.
quelle
Hier ist eine einfache Variante, wenn Sie nicht daran interessiert sind, sie zu einer generischen Funktion zu machen:
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)
quelle
Lösung mit
ProductCompletion
aus Dolmetscherpaket:import tools.nsc.interpreter.ProductCompletion def getCCParams(cc: Product) = { val pc = new ProductCompletion(cc) pc.caseNames.zip(pc.caseFields).toMap }
quelle
Sie könnten formlos verwenden.
Lassen
case class X(a: Boolean, b: String,c:Int) case class Y(a: String, b: String)
Definieren Sie 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 Typklassen, um die toMap-Methoden bereitzustellen
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 so verwenden.
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) }
welche druckt
Überprüfen Sie für verschachtelte Fallklassen (also verschachtelte Karten) eine andere Antwort
quelle
Wenn Sie Json4s verwenden, können Sie Folgendes tun:
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]]
quelle
Ich weiß nicht, wie schön es ist ... aber das scheint zu funktionieren, zumindest für dieses sehr, sehr einfache Beispiel. Es braucht wahrscheinlich etwas Arbeit, könnte aber ausreichen, um Ihnen den Einstieg zu erleichtern? Grundsätzlich werden alle "bekannten" Methoden aus einer Fallklasse (oder einer anderen Klasse: /) herausgefiltert.
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) } }
quelle
commons.mapper.Mappers.Mappers.beanToMap(caseClassBean)
Details: https://github.com/hank-whu/common4s
quelle
Mit der Verwendung von Java Reflection, aber ohne Änderung der Zugriffsebene. Konvertiert
Product
und Fallklasse inMap[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) } }
quelle