Бесформенный код для преобразования Map [String, Any] в класс case не может обрабатывать необязательные подструктуры - PullRequest
1 голос
/ 07 марта 2019

Я пытаюсь использовать это https://stackoverflow.com/a/31641779/1586965 ( Как использовать бесформенное преобразование универсальной карты [String, Any] в класс case внутри обобщенной функции? ) для обработки

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

Если подструктура не является вложенной или имеет значение None, то тесты работают нормально.

Я тоже пробовал это, но это тоже не работает

"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 Ответ

1 голос
/ 08 марта 2019

Если вы хотите, чтобы код работал с 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)

  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...