scala shapeless

scala - Convertir clases de casos anidados en mapas anidados utilizando Shapeless



(2)

Estoy tratando de resolver this pregunta usando Shapeless; en resumen, se trata de convertir una clase de caso anidada a Map [String, Any], aquí está el ejemplo:

case class Person(name:String, address:Address) case class Address(street:String, zip:Int) val p = Person("Tom", Address("Jefferson st", 10000))

Quiere convertir p a siguiente:

Map("name" -> "Tom", "address" -> Map("street" -> "Jefferson st", "zip" -> 10000))

Estoy tratando de hacerlo usando Shapeless LabelledGeneric , esto es lo que he hecho hasta ahora:

import shapeless._ import record._, syntax.singleton._ import ops.record._ import shapeless.ops.record._ def writer[T,A<:HList,H<:HList](t:T) (implicit lGeneric:LabelledGeneric.Aux[T,A], kys:Keys.Aux[A,H], vls:Values[A]) = { val tGen = lGeneric.to(t) val keys = Keys[lGeneric.Repr].apply val values = Values[lGeneric.Repr].apply(tGen) println(keys) println(values) }

Estoy tratando de tener un escritor recursivo para verificar cada valor y tratar de hacer un Mapa para cada elemento en valor. El código anterior funciona bien, pero cuando quiero iterar sobre los values con una muestra Poly, usando el siguiente código obtuve estos errores.

values.map(identity) //or tGen.map(identity) Error:(75, 19) could not find implicit value for parameter mapper: shapeless.ops.hlist.FlatMapper[shapeless.poly.identity.type,vls.Out] values.flatMap(identity) ^ Error:(75, 19) not enough arguments for method flatMap: (implicit mapper: shapeless.ops.hlist.FlatMapper[shapeless.poly.identity.type,vls.Out])mapper.Out. Unspecified value parameter mapper. values.flatMap(identity) ^

No sé por qué estoy recibiendo ese error. También me encantaría saber si hay una forma más fácil de hacer todo usando Shapeless.


Cada vez que desee realizar una operación como flatMap en una lista de HList cuyo tipo no se conoce estáticamente, deberá proporcionar evidencia (en forma de un parámetro implícito) de que la operación está realmente disponible para ese tipo. Esta es la razón por la que el compilador se queja de que faltan instancias de FlatMapper : no sabe cómo flatMap(identity) sobre un HList arbitrario sin ellos.

Una forma más limpia de lograr este tipo de cosas sería definir una clase de tipo personalizada. Shapeless ya proporciona una clase de tipo ToMap para los registros, y podemos tomarla como punto de partida, aunque no proporciona exactamente lo que está buscando (no funciona recursivamente en clases de casos anidados).

Podemos escribir algo como lo siguiente:

import shapeless._, labelled.FieldType, record._ trait ToMapRec[L <: HList] { def apply(l: L): Map[String, Any] }

Ahora necesitamos proporcionar instancias para tres casos. El primer caso es el caso base, el registro vacío, y es manejado por hnilToMapRec continuación.

El segundo caso es el caso en el que sabemos cómo convertir la cola del registro, y sabemos que la cabeza es algo que también podemos convertir recursivamente ( hconsToMapRec0 aquí).

El caso final es similar, pero para los jefes que no tienen instancias de hconsToMapRec1 ( hconsToMapRec1 ). Tenga en cuenta que necesitamos usar un rasgo de LowPriority para asegurarnos de que esta instancia tenga la prioridad adecuada con respecto a hconsToMapRec0 si no lo hconsToMapRec0 , los dos tendrían la misma prioridad y obtendríamos errores sobre las instancias ambiguas.

trait LowPriorityToMapRec { implicit def hconsToMapRec1[K <: Symbol, V, T <: HList](implicit wit: Witness.Aux[K], tmrT: ToMapRec[T] ): ToMapRec[FieldType[K, V] :: T] = new ToMapRec[FieldType[K, V] :: T] { def apply(l: FieldType[K, V] :: T): Map[String, Any] = tmrT(l.tail) + (wit.value.name -> l.head) } } object ToMapRec extends LowPriorityToMapRec { implicit val hnilToMapRec: ToMapRec[HNil] = new ToMapRec[HNil] { def apply(l: HNil): Map[String, Any] = Map.empty } implicit def hconsToMapRec0[K <: Symbol, V, R <: HList, T <: HList](implicit wit: Witness.Aux[K], gen: LabelledGeneric.Aux[V, R], tmrH: ToMapRec[R], tmrT: ToMapRec[T] ): ToMapRec[FieldType[K, V] :: T] = new ToMapRec[FieldType[K, V] :: T] { def apply(l: FieldType[K, V] :: T): Map[String, Any] = tmrT(l.tail) + (wit.value.name -> tmrH(gen.to(l.head))) } }

Por último proporcionamos un poco de sintaxis para mayor comodidad:

implicit class ToMapRecOps[A](val a: A) extends AnyVal { def toMapRec[L <: HList](implicit gen: LabelledGeneric.Aux[A, L], tmr: ToMapRec[L] ): Map[String, Any] = tmr(gen.to(a)) }

Y luego podemos demostrar que funciona:

scala> p.toMapRec res0: Map[String,Any] = Map(address -> Map(zip -> 10000, street -> Jefferson st), name -> Tom)

Tenga en cuenta que esto no funcionará para los tipos en los que las clases de casos anidadas están en una lista, tupla, etc., pero podría extenderlo a esos casos de manera bastante sencilla.


Tengo un problema con un enfoque proporcionado por Travis Brown.
Algunas de las clases de casos de anidamiento no se convierten a Mapa https://scalafiddle.io/sf/cia2jTa/0 .

La respuesta fue encontrada here .
Para corregir la solución, simplemente ajuste ToMapRec [T] en parámetros implícitos a Lazy [ToMapRec [T]]. Fiddle corregido https://scalafiddle.io/sf/cia2jTa/1