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