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.

War es hilfreich?

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 classes (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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top