instanciar - Clase de caso para mapear en Scala
scala object (9)
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)
¿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.
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.
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))
}
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)
}
}
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 anidados (por lo tanto, mapas anidados) verifique otra respuesta
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 anidados en mapas en cualquier nivel de anidación.
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]]
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
}
commons.mapper.Mappers.Mappers.beanToMap(caseClassBean)
Detalles: https://github.com/hank-whu/common4s