web-dev-qa-db-ger.com

In Scala abzubildende Fallklasse

Gibt es eine nette Möglichkeit, eine Scala case class-Instanz zu konvertieren, z.

case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")

in eine Abbildung irgendeiner Art, z.

getCCParams(x) returns "param1" -> "hello", "param2" -> "world"

Das funktioniert für jede Fallklasse, 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 für die Felder der Fallklassen. Ich könnte 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 beschädigt werden könnte, wenn sich die zugrunde liegende Implementierung von Fallklassen ändert.

Momentan arbeite ich an einem Scala-Server und definiere das Protokoll und alle seine Meldungen und Ausnahmen mit Hilfe von Fallklassen, da sie ein so schönes, prägnantes Konstrukt dafür sind. Ich muss sie dann jedoch in eine Java-Map übersetzen, um die Messaging-Schicht für jede Client-Implementierung zu verwenden. Meine derzeitige Implementierung definiert nur eine Übersetzung für jede Fallklasse separat, aber es wäre schön, eine verallgemeinerte Lösung zu finden.

68
Will

Das sollte funktionieren:

def getCCParams(cc: AnyRef) =
  (Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
    f.setAccessible(true)
    a + (f.getName -> f.get(cc))
  }
81
Walter Chang

Da Fallklassen Product erweitern, können Sie einfach mit .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 setAccessible für das Feld aufrufen müssen, um den Wert zu lesen. Ein anderer ist, dass productIterator keine Reflektion verwendet.

Beachten Sie, dass dieses Beispiel mit einfachen Fallklassen funktioniert, die keine anderen Klassen erweitern und keine Felder außerhalb des Konstruktors deklarieren.

35
Andrejs

Wenn jemand nach einer rekursiven Version sucht, ist hier die Änderung 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
}

Es erweitert auch die verschachtelten Fallklassen auf jeder Verschachtelungsebene in Karten.

12

Hier ist eine einfache Variante, wenn Sie keine generische Funktion benötigen:

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)
5
ShawnFumo

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 Typenklassen, 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 kannst du es so benutzen.

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 

anyMapX = Map (c -> 26, b -> Fahrrad, a -> true)

anyMapY = Map (b -> Sekunde, a -> Erste)

stringMapX = Map (c -> 26, b -> bike, a -> true)

stringMapY = Map (b -> Sekunde, a -> Erste)

Für verschachtelte Fallklassen (also verschachtelte Karten) Eine andere Antwort

4
Harry Laou

Lösung mit ProductCompletion aus dem Interpreterpaket:

import tools.nsc.interpreter.ProductCompletion

def getCCParams(cc: Product) = {
  val pc = new ProductCompletion(cc)
  pc.caseNames.Zip(pc.caseFields).toMap
}
4

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]]
3
Barak BN

Ich weiß nichts über Nizza ... aber das scheint zumindest für dieses sehr grundlegende Beispiel zu funktionieren. Es braucht wahrscheinlich etwas Arbeit, reicht aber aus, um den Anfang zu machen? 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)
  }
}
2
André Laszlo
commons.mapper.Mappers.Mappers.beanToMap(caseClassBean)

Details: https://github.com/hank-whu/common4s

2
Kai Han

Ab Scala 2.13 werden case classes (als Implementierungen von Product ) mit einer productElementNames -Methode versehen, die einen Iterator über die Feldnamen zurückgibt.

Durch das Zippen von Feldnamen mit Feldwerten, die mit productIterator erhalten werden, können wir generell 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")
1
Xavier Guihot