Clase de caso para mapear en Scala

¿Alguien sabe si hay una buena manera de convertir una instancia de clase de caso de Scala, por ejemplo,

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

En un mapeo de algún tipo, por ejemplo

 getCCParams(x) returns "param1" -> "hi", "param2" -> "3" 

Lo cual funciona para cualquier clase de caso, no solo las predefinidas. Descubrí que puede sacar el nombre de la clase de caso escribiendo un método que interroga a la clase de Producto subyacente, por ej.

 def getCCName(caseobj: Product) = caseobj.productPrefix getCCName(x) returns "MyClass" 

Así que estoy buscando una solución similar, pero para los campos de clase de caso. Me imagino que una solución podría tener que usar la reflexión de Java, pero odiaría escribir algo que podría romperse en una versión futura de Scala si la implementación subyacente de las clases de casos cambia.

Actualmente estoy trabajando en un servidor Scala y definiendo el protocolo y todos sus mensajes y excepciones utilizando clases de casos, ya que son una construcción hermosa y concisa para esto. Pero luego necesito traducirlos a un mapa de Java para enviarlos a través de la capa de mensajes para que los use cualquier implementación de cliente. Mi implementación actual solo define una traducción para cada clase de caso por separado, pero sería bueno encontrar una solución generalizada.

Esto debería funcionar:

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

Debido a que las clases de caso se extienden, Product one simplemente puede usar .productIterator para obtener los valores de campo:

 def getCCParams(cc: Product) = cc.getClass.getDeclaredFields.map( _.getName ) // all field names .zip( cc.productIterator.to ).toMap // zipped with all values 

O alternativamente:

 def getCCParams(cc: Product) = { val values = cc.productIterator cc.getClass.getDeclaredFields.map( _.getName -> values.next ).toMap } 

Una ventaja del producto es que no necesita llamar a setAccessible en el campo para leer su valor. Otra es que productIterator no usa reflexión.

Tenga en cuenta que este ejemplo funciona con clases de casos simples que no amplían otras clases y no declaran campos fuera del constructor.

Si alguien busca una versión recursiva, aquí está la modificación de la solución de @ 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 } 

También expande las clases de casos nesteds en mapas en cualquier nivel de anidación.

Aquí hay una variante simple si no te importa convertirla en una función genérica:

 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) 

Solución con ProductCompletion del paquete de intérprete:

 import tools.nsc.interpreter.ProductCompletion def getCCParams(cc: Product) = { val pc = new ProductCompletion(cc) pc.caseNames.zip(pc.caseFields).toMap } 

Podría usar sin forma.

Dejar

 case class X(a: Boolean, b: String,c:Int) case class Y(a: String, b: String) 

Definir una representación genérica etiquetada

 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] } 

Defina dos tipos de clases para proporcionar los métodos 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 } } } 

Entonces puedes usarlo así.

 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) } 

que imprime

anyMapX = Mapa (c -> 26, b -> bicicleta, a -> verdadero)

anyMapY = Mapa (b -> segundo, a -> primero)

stringMapX = Mapa (c -> 26, b -> bicicleta, a -> verdadero)

stringMapY = Mapa (b -> segundo, a -> primero)

Para las clases de casos nesteds (por lo tanto, mapas nesteds) verifique otra respuesta

Si está utilizando Json4s, podría hacer lo siguiente:

 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]] 

No sé nada de bueno … pero parece que funciona, al menos para este ejemplo muy básico. Probablemente necesite algo de trabajo, pero ¿podría ser suficiente para comenzar? Básicamente, filtra todos los métodos “conocidos” de una clase de caso (o de cualquier otra clase: /)

 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) 

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