Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

The question here asks about mapping a case class to a Map[String,Any]. I was wondering what would be the other way around, converting Map[String,Any] to a case class. Given the following map:

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

Convert it to a case class of Person:

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

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

with something like this:

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

How should I do that with Shapeless.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
688 views
Welcome To Ask or Share your Answers For Others

1 Answer

Here's an off-the-cuff, mostly untested solution. First for the type class:

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

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

And then the instances:

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
  }
}

And then a helper class for convenience:

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]

And the example:

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)
)

And finally:

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

This will only work for case classes whose members are either Typeable or other case classes whose members are either Typeable or other case classes… (and so on).

Gotcha

Careful not to have scala.reflect.runtime.universe._ in your imports, as this will break the above.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...