Преобразование карты [String, Any] в класс case с использованием Shapeless

Вопрос здесь задает вопрос о сопоставлении класса case с Map [String, Any]. Мне было интересно, что будет наоборот, преобразование Map [String, Any] в класс case. Учитывая следующую карту:

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

Преобразуйте его в класс case Person:

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

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

с чем-то вроде этого:

val newP = mp.asCC[Person]
assert(newP.get == p)

Как мне сделать это с помощью Shapeless.

Ответ 1

Здесь отложено, в основном непроверенное решение. Сначала для класса типа:

import shapeless._, labelled.{ FieldType, field }

trait FromMap[L <: HList] {
  def apply(m: Map[String, Any]): Option[L]
}

И затем экземпляры:

trait LowPriorityFromMap {
  implicit def hconsFromMap1[K <: Symbol, V, T <: HList](implicit
    witness: Witness.Aux[K],
    typeable: Typeable[V],
    fromMapT: Lazy[FromMap[T]]
  ): FromMap[FieldType[K, V] :: T] = new FromMap[FieldType[K, V] :: T] {
    def apply(m: Map[String, Any]): Option[FieldType[K, V] :: T] = for {
      v <- m.get(witness.value.name)
      h <- typeable.cast(v)
      t <- fromMapT.value(m)
    } yield field[K](h) :: t
  }
}

object FromMap extends LowPriorityFromMap {
  implicit val hnilFromMap: FromMap[HNil] = new FromMap[HNil] {
    def apply(m: Map[String, Any]): Option[HNil] = Some(HNil)
  }

  implicit def hconsFromMap0[K <: Symbol, V, R <: HList, T <: HList](implicit
    witness: Witness.Aux[K],
    gen: LabelledGeneric.Aux[V, R],
    fromMapH: FromMap[R],
    fromMapT: FromMap[T]
  ): FromMap[FieldType[K, V] :: T] = new FromMap[FieldType[K, V] :: T] {
    def apply(m: Map[String, Any]): Option[FieldType[K, V] :: T] = for {
      v <- m.get(witness.value.name)
      r <- Typeable[Map[String, Any]].cast(v)
      h <- fromMapH(r)
      t <- fromMapT(m)
    } yield field[K](gen.from(h)) :: t
  }
}

А затем вспомогательный класс для удобства:

class ConvertHelper[A] {
  def from[R <: HList](m: Map[String, Any])(implicit
    gen: LabelledGeneric.Aux[A, R],
    fromMap: FromMap[R]
  ): Option[A] = fromMap(m).map(gen.from(_))
}

def to[A]: ConvertHelper[A] = new ConvertHelper[A]

И пример:

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

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

И наконец:

scala> to[Person].from(mp)
res0: Option[Person] = Some(Person(Tom,Address(Jefferson st,10000)))

Это будет работать только для классов case, членами которых являются Typeable или другие классы case, члены которых либо Typeable, либо другие классы case... (и т.д.).