2015-07-26 2 views
17

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

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

Преобразовать его в случае класса 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) 

Как я должен сделать это с бесформенными.

ответ

23

Вот откладывание, в основном непроверенное решение. Во-первых для класса типа:

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

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

+0

Изменив пример следующим образом, он не работает. Есть идеи? 'случай класс Адрес (улица: String, почтовый индекс: Int, состояние: String)' Вал пл = Map ("название" ' -> "Том", "адрес" -> Map ("улица" -> «Jefferson st», «zip» -> 10000, «state» -> «CA») ) ' – lambdista

+0

@ lambdista Это была проблема расхождения - я только что отредактировал ответ, чтобы быть более надежным. –

+0

@TravisBrown Как это можно адаптировать, чтобы некоторые из значений на Карте были «нулевыми». Например, '' zip "-> null'. – ISJ