将Map [String,Any]转换为case类的无形代码无法处理可选的子结构

时间:2019-03-07 11:02:12

标签: scala generic-programming shapeless

我正在尝试使用此https://stackoverflow.com/a/31641779/1586965How to use shapeless to convert generic Map[String, Any] to case class inside generic function?)来处理

case class Address(street: String, zip: Int)
case class PersonOptionalAddress(name: String, address: Option[Address])

我的测试失败:

"Convert Map to PersonOptionalAddress Some" in {
  CaseClassFromMap[PersonOptionalAddress](Map(
    "name" -> "Tom",
    "address" -> Some(Map("street" -> "Jefferson st", "zip" -> 10000))
  )) must_=== PersonOptionalAddress("Tom", Some(Address("Jefferson st", 10000)))
}

使用

java.util.NoSuchElementException: None.get

如果子结构未嵌套或为无,则测试正常。

我也尝试过这种方法,但是它也不起作用

"Convert Map to PersonOptionalAddress Some" in {
  CaseClassFromMap[PersonOptionalAddress](Map(
    "name" -> "Tom",
    "address" -> Map("x" -> Map("street" -> "Jefferson st", "zip" -> 10000))
  )) must_=== PersonOptionalAddress("Tom", Some(Address("Jefferson st", 10000)))
}

1 个答案:

答案 0 :(得分:1)

如果您希望代码与PersonOptionalAddress一起使用,则应再添加一个类型类的实例,以便它也可以与Map( "name" -> "Tom", "address" -> Some(Map ...) )一起使用

implicit def hconsFromMap0opt[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, Option[V]] :: T] =
      new FromMap[FieldType[K, Option[V]] :: T] {
        def apply(m: Map[String, Any]): Option[FieldType[K, Option[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](Some(gen.from(h))) :: t).orElse(for {
          v <- m.get(witness.value.name)
          r1 <- Typeable[Option[Map[String, Any]]].cast(v)
          opt = for {
            r <- r1
            h <- fromMapH(r)
          } yield gen.from(h)
          t <- fromMapT(m)
        } yield field[K](opt) :: t)
      }

整个代码

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

object App {

  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
      }

    implicit def hconsFromMap0opt[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, Option[V]] :: T] =
      new FromMap[FieldType[K, Option[V]] :: T] {
        def apply(m: Map[String, Any]): Option[FieldType[K, Option[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](Some(gen.from(h))) :: t).orElse(for {
          v <- m.get(witness.value.name)
          r1 <- Typeable[Option[Map[String, Any]]].cast(v)
          opt = for {
            r <- r1
            h <- fromMapH(r)
          } yield gen.from(h)
          t <- fromMapT(m)
        } yield field[K](opt) :: t)
      }

  trait CaseClassFromMap[P <: Product] {
    def apply(m: Map[String, Any]): Option[P]
  }

  object CaseClassFromMap {
    implicit def mk[P <: Product, R <: HList](implicit gen: LabelledGeneric.Aux[P, R],
                                              fromMap: FromMap[R]): CaseClassFromMap[P] = new CaseClassFromMap[P] {
      def apply(m: Map[String, Any]): Option[P] = fromMap(m).map(gen.from)
    }

    def apply[P <: Product](map: Map[String, Any])(implicit fromMap: CaseClassFromMap[P]): P = fromMap(map).get
  }

  case class Address(street: String, zip: Int)
  case class PersonOptionalAddress(name: String, address: Option[Address])
  case class PersonAddress(name: String, address: Address)

  def main(args: Array[String]): Unit = {
    println(
      CaseClassFromMap[PersonAddress](Map(
        "name" -> "Tom",
        "address" -> Map("street" -> "Jefferson st", "zip" -> 10000)
      ))
    )//PersonAddress(Tom,Address(Jefferson st,10000))

    println(
      CaseClassFromMap[PersonOptionalAddress](Map(
        "name" -> "Tom",
        "address" -> Map("street" -> "Jefferson st", "zip" -> 10000)
      ))
    )//PersonOptionalAddress(Tom,Some(Address(Jefferson st,10000)))

    println(
      CaseClassFromMap[PersonOptionalAddress](Map(
        "name" -> "Tom",
        "address" -> Some(Map("street" -> "Jefferson st", "zip" -> 10000))
      ))
    )//PersonOptionalAddress(Tom,Some(Address(Jefferson st,10000)))

    println(
      CaseClassFromMap[PersonOptionalAddress](Map(
        "name" -> "Tom",
        "address" -> None
      ))
    )//PersonOptionalAddress(Tom,None)

  }
}