任何时候你想执行类似的操作flatMap
on an HList
如果其类型不是静态已知的,则您需要提供证据(以隐式参数的形式)来证明该操作实际上可用于该类型。这就是编译器抱怨缺少的原因FlatMapper
实例——它不知道如何flatMap(identity)
超过任意的HList
没有他们。
完成此类事情的一种更简洁的方法是定义自定义类型类。 Shapeless 已经提供了ToMap https://github.com/milessabin/shapeless/blob/698daa1e08ced007c14a9c5303ba094815ad6f1c/core/src/main/scala/shapeless/ops/records.scala#L339记录的类型类,我们可以将其作为起点,尽管它没有提供您正在寻找的确切内容(它不能在嵌套案例类上递归地工作)。
我们可以写如下内容:
import shapeless._, labelled.FieldType, record._
trait ToMapRec[L <: HList] { def apply(l: L): Map[String, Any] }
现在我们需要提供三种情况的实例。第一种情况是基本情况——空记录——它由hnilToMapRec
below.
第二种情况是我们知道如何转换记录的尾部,并且我们知道头是我们也可以递归转换的情况(hconsToMapRec0
here).
最后的情况类似,但对于没有的头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)
请注意,这不适用于嵌套案例类位于列表、元组等中的类型,但您可以非常简单地将其扩展到这些案例。