使用 Shapeless 将嵌套大小写 类 转换为嵌套地图

Converting nested case classes to nested Maps using Shapeless

我正在尝试使用 Shapeless 解决 [this][1] 问题,总而言之,它是关于将嵌套大小写 class 转换为 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))

目标是将 p 转换为以下内容:

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

我正在尝试使用 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)
  }

我正在尝试让递归编写器检查每个值并尝试为值中的每个元素创建 Map。上面的代码工作正常,但是当我想使用示例 Poly 迭代 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)
                  ^

我不知道为什么会出现该错误。我也很高兴知道是否有更简单的方法使用 Shapeless 来完成整个事情。 [1]: Scala macros for nested case classes to Map and other way around

任何时候你想在类型不是静态已知的 HList 上执行像 flatMap 这样的操作,你需要提供证据(以隐式参数的形式)该操作实际上可用于该类型。这就是编译器抱怨缺少 FlatMapper 个实例的原因——它不知道如何 flatMap(identity) 超过没有它们的任意 HList

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

我们可以这样写:

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

trait ToMapRec[L <: HList] { def apply(l: L): Map[String, Any] }

现在我们需要提供三种情况的实例。第一种情况是基本情况——空记录——它由下面的 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)))
  }
}

最后,为了方便起见,我们提供了一些语法:

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

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

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

请注意,这不适用于嵌套大小写 classes 在列表、元组等中的类型,但您可以非常直接地将它扩展到这些情况。

我对 Travis Brown 提供的方法有疑问。
一些嵌套案例 类 没有转换为 Map https://scalafiddle.io/sf/cia2jTa/0

已找到答案 here
要更正解决方案,只需将 ToMapRec[T] 包装在 Lazy[ToMapRec[T]] 的隐式参数中。已更正 fiddle https://scalafiddle.io/sf/cia2jTa/1