使用Shapeless将嵌套的case类转换为嵌套的Maps

Omi*_*mid 16 scala shapeless

我试图使用Shapeless 来解决这个问题,总结一下,它是关于将嵌套的case类转换为Map [String,Any],这是示例:

case class Person(name:String, address:Address)
case class Address(street:String, zip:Int)

val p = Person("Tom", Address("Jefferson st", 10000))
Run Code Online (Sandbox Code Playgroud)

它想转换p为以下内容:

Map("name" -> "Tom", "address" -> Map("street" -> "Jefferson st", "zip" -> 10000))
Run Code Online (Sandbox Code Playgroud)

我正在尝试使用Shapeless LabelledGeneric,这是我迄今为止所做的:

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)
  }
Run Code Online (Sandbox Code Playgroud)

我正在尝试使用递归编写器来检查每个值,并尝试为值中的每个元素创建Map.上面的代码工作正常但是当我想要values使用以下代码迭代时,我得到了这些错误.

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)
                  ^
Run Code Online (Sandbox Code Playgroud)

我不知道为什么我会收到这个错误.我也很高兴知道使用Shapeless是否有更简单的方法来做整件事.

Tra*_*own 24

任何时候,要像执行操作flatMapHList,其类型不是静态知道,你需要提供证据(在隐含参数的形式),该操作实际可用该类型.这就是为什么编译器抱怨缺少FlatMapper实例的原因 - 它不知道如何flatMap(identity)HList没有它们的情况下覆盖任意实例.

完成此类事情的更简洁方法是定义自定义类型类.Shapeless已经ToMap为记录提供了一个类型类,我们可以把它作为一个起点,虽然它没有提供你正在寻找的东西(它在嵌套的case类中不能递归地工作).

我们可以写如下内容:

import shapeless._, labelled.FieldType, record._

trait ToMapRec[L <: HList] { def apply(l: L): Map[String, Any] }
Run Code Online (Sandbox Code Playgroud)

现在我们需要为三种情况提供实例.第一种情况是基本情况 - 空记录 - 它由hnilToMapRec下面处理.

第二种情况是我们知道如何转换记录尾部的情况,我们知道头部是我们也可以递归转换的(hconsToMapRec0这里).

最后的情况类似,但对于没有ToMapRec实例的头(hconsToMapRec1).请注意,我们需要使用LowPriority特征来确保此实例的优先级正确hconsToMapRec0- 如果我们没有,两者将具有相同的优先级,我们会得到有关模糊实例的错误.

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)))
  }
}
Run Code Online (Sandbox Code Playgroud)

最后,我们提供了一些方便的语法:

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))
}
Run Code Online (Sandbox Code Playgroud)

然后我们可以证明它有效:

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

请注意,这对于嵌套案例类在列表,元组等中的类型不起作用,但您可以非常直接地将它扩展到这些情况.