【问题标题】:Converting nested case classes to nested Maps using Shapeless使用 Shapeless 将嵌套案例类转换为嵌套地图
【发布时间】:2015-10-16 11:39:39
【问题描述】:

我正在尝试使用 Shapeless 解决 [this][1] 问题,总而言之,它是将嵌套案例类转换为 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

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

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

    完成这种事情的一种更简洁的方法是定义一个自定义类型类。 Shapeless 已经为记录提供了一个ToMap 类型类,我们可以将其作为一个起点,尽管它并没有提供您正在寻找的确切内容(它不能在嵌套的案例类上递归地工作)。

    我们可以这样写:

    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)
    

    请注意,这不适用于嵌套案例类位于列表、元组等中的类型,但您可以非常简单地将其扩展到这些案例。

    【讨论】:

    • 感谢神奇的魔法!一个问题,另一种方式,从 Map[String,Any] 到 case 类,有没有好办法,因为我们丢失了所有类型?
    • @Omid 太棒了!不过,这可能值得提出一个新问题。 :)
    • @Omid,哦,不,抱歉——您需要ToMapRec 来表示A,而不是A 本身。可能值得提出一个新问题,以避免进一步引出 cmets。
    • @Omid 不是返回ToMapRec[FieldType[K, V] :: T],而是返回ToMapRec[FieldType[K, List[V]] :: T] 等的方法。可能值得提出一个新问题。
    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-09
      • 1970-01-01
      • 1970-01-01
      • 2014-04-09
      • 1970-01-01
      • 2020-11-12
      • 2016-11-14
      相关资源
      最近更新 更多