使用 Shapeless 将嵌套的 case 类转换为嵌套的 Maps [英] Converting nested case classes to nested Maps using Shapeless

查看:43
本文介绍了使用 Shapeless 将嵌套的 case 类转换为嵌套的 Maps的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试解决这个使用Shapeless的问题,总而言之,它是关于将嵌套的case类转换为Map[String,Any],这是示例:

I am trying to solve this question using Shapeless, in summary it's about converting a nested case class to Map[String,Any], here is the example:

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

val p = Person("Tom", Address("Jefferson st", 10000))

它想将 p 转换为以下内容:

It wants to convert p to following:

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

我正在尝试使用 Shapeless LabelledGeneric 来做到这一点,这是我目前所做的:

I am trying to do it using Shapeless LabelledGeneric, here is what I for so far:

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 时,使用以下代码我得到了这些错误.

I am trying to have a recursive writer to check each value and try to make Map for each element in value. The above code works fine but when I want iterate over values with a sample Poly, using the following code I got these errors.

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 来完成整个过程.

I don't know why I'm getting that error. I also would be happy to know if there is an easier way to do the whole thing using Shapeless.

推荐答案

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

Any time you want to perform an operation like flatMap on an HList whose type isn't statically known, you'll need to provide evidence (in the form of an implicit parameter) that the operation is actually available for that type. This is why the compiler is complaining about missing FlatMapper instances—it doesn't know how to flatMap(identity) over an arbitrary HList without them.

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

A cleaner way to accomplish this kind of thing would be to define a custom type class. Shapeless already provides a ToMap type class for records, and we can take it as a starting point, although it doesn't provide exactly what you're looking for (it doesn't work recursively on nested case classes).

我们可以这样写:

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

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

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

Now we need to provide instances for three cases. The first case is the base case—the empty record—and it's handled by hnilToMapRec below.

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

The second case is the case where we know how to convert the tail of the record, and we know that the head is something that we can also recursively convert (hconsToMapRec0 here).

最后一种情况类似,但对于没有 ToMapRec 实例 (hconsToMapRec1) 的头部.请注意,我们需要使用 LowPriority trait 来确保此实例相对于 hconsToMapRec0 具有正确的优先级 - 如果我们不这样做,则两者将具有相同的优先级并且我们会收到关于不明确实例的错误.

The final case is similar, but for heads that don't have ToMapRec instances (hconsToMapRec1). Note that we need to use a LowPriority trait to make sure that this instance is prioritized properly with respect to hconsToMapRec0—if we didn't, the two would have the same priority and we'd get errors about ambiguous instances.

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

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

Lastly we provide some syntax for convenience:

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

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

And then we can demonstrate that it works:

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

请注意,这不适用于嵌套 case 类位于列表、元组等中的类型,但您可以非常直接地将其扩展到这些 case.

Note that this won't work for types where the nested case classes are in a list, tuple, etc., but you could extend it to those cases pretty straightforwardly.

这篇关于使用 Shapeless 将嵌套的 case 类转换为嵌套的 Maps的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆