Преобразование вложенных классов дел во вложенные Карты с помощью Shapeless

Я пытаюсь решить этот вопрос с помощью Shapeless, в итоге речь идет о преобразовании вложенного класса case в 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)
  }

Я пытаюсь иметь рекурсивный писатель, чтобы проверить каждое значение и попытаться сделать карту для каждого элемента в значении. Приведенный выше код работает нормально, но когда я хочу перебрать values с примером Poly, используя следующий код, я получил эти ошибки.

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.

2 ответа

Решение

В любое время вы хотите выполнить такую ​​операцию, как flatMap на HList чей тип статически не известен, вам нужно предоставить подтверждение (в форме неявного параметра), что операция действительно доступна для этого типа. Вот почему компилятор жалуется на отсутствие FlatMapper случаи - он не знает, как flatMap(identity) над произвольным HList без них.

Более понятный способ сделать это - определить класс пользовательского типа. Бесформенный уже обеспечивает ToMap тип class для записей, и мы можем взять его как отправную точку, хотя он не обеспечивает именно то, что вы ищете (он не работает рекурсивно на вложенных классах case).

Мы можем написать что-то вроде следующего:

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 https://scalafiddle.io/sf/cia2jTa/0.

Ответ был найден здесь.
Чтобы исправить решение, просто оберните ToMapRec[T] в неявных параметрах в Lazy[ToMapRec[T]]. Исправленная скрипка https://scalafiddle.io/sf/cia2jTa/1

Другие вопросы по тегам