Last active
April 12, 2020 15:21
-
-
Save knutwalker/ac18c4c59e3fd96a771e to your computer and use it in GitHub Desktop.
Converting Map[String,Any] to a case class using Shapeless - http://stackoverflow.com/q/31640565/2996265
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Based on http://stackoverflow.com/a/31641779/2996265 | |
* | |
* Changed: | |
* - use https://github.com/knutwalker/validation to accumulate errors | |
* - support lists as well (polymorphic on higher kinds would be better, though) | |
* - use -Xexperimental for java8 style SAM support | |
*/ | |
import validation.{ NonEmptyVector, Result } | |
import shapeless._ | |
import shapeless.labelled._ | |
trait FromMap[R <: HList] extends ((Map[String, Any], Option[String]) ⇒ Result[String, R]) { | |
def apply(m: Map[String, Any], parent: Option[String]): Result[String, R] | |
} | |
trait LowPriorityFromMap0 { | |
implicit def hconsFromMap0[K <: Symbol, V, T <: HList]( | |
implicit | |
K: Witness.Aux[K], | |
V: Typeable[V], | |
T: Lazy[FromMap[T]] | |
): FromMap[FieldType[K, V] :: T] = (m, p) ⇒ { | |
val name = K.value.name | |
val fullName = p.fold(name)(_ + s".$name") | |
val value = Result | |
.fromOption(m.get(name), s"There is no entry named [$fullName] in the map.") | |
.flatMap(v ⇒ Result.fromOption(V.cast(v), s"The value [$v] under [$fullName] is not of type [${V.describe}]")) | |
val tail = T.value(m, p) | |
(value and tail) ((v, t) ⇒ field[K](v) :: t) | |
} | |
} | |
trait LowPriorityFromMap1 extends LowPriorityFromMap0 { | |
implicit def hconsFromMap1[K <: Symbol, V, R <: HList, T <: HList]( | |
implicit | |
K: Witness.Aux[K], | |
V: LabelledGeneric.Aux[V, R], | |
R: Lazy[FromMap[R]], | |
T: Lazy[FromMap[T]] | |
): FromMap[FieldType[K, V] :: T] = (m, p) ⇒ { | |
val name = K.value.name | |
val fullName = p.fold(name)(_ + s".$name") | |
val value = Result | |
.fromOption(m.get(name), s"There is no entry named [$fullName] in the map.") | |
.flatMap(k ⇒ Result.fromOption(Typeable[Map[String, Any]].cast(k), s"The value under [$fullName] is not a nested map.")) | |
.flatMap(v ⇒ R.value(v, Some(fullName))) | |
val tail = T.value(m, p) | |
(value and tail) ((r, t) ⇒ field[K](V.from(r)) :: t) | |
} | |
} | |
object FromMap extends LowPriorityFromMap1 { | |
private[this] val some_hnil = Result.valid(HNil) | |
implicit val hnilFromMap: FromMap[HNil] = (m, p) ⇒ some_hnil | |
implicit def hconsFromMap2[K <: Symbol, V, R <: HList, T <: HList]( | |
implicit | |
K: Witness.Aux[K], | |
V: LabelledGeneric.Aux[V, R], | |
R: Lazy[FromMap[R]], | |
T: Lazy[FromMap[T]] | |
): FromMap[FieldType[K, List[V]] :: T] = (m, p) ⇒ { | |
val name = K.value.name | |
val fullName = p.fold(name)(_ + s".$name") | |
val value: Result[String, List[R]] = Result | |
.fromOption(m.get(name), s"There is no entry named [$fullName] in the map.") | |
.flatMap(k ⇒ Result.fromOption(Typeable[List[Map[String, Any]]].cast(k), s"The value under [$fullName] is not a nested list of maps.")) | |
.flatMap(xs ⇒ Result.traverse(xs.zipWithIndex)({ case (v, i) ⇒ R.value(v, Some(s"$fullName.$i"))})) | |
val tail = T.value(m, p) | |
(value and tail) ((r, t) ⇒ field[K](r.map(V.from)) :: t) | |
} | |
class ConverterHelper[A] { | |
def run[R <: HList](m: Map[String, Any])( | |
implicit | |
A: LabelledGeneric.Aux[A, R], | |
R: FromMap[R] | |
): Result[String, A] = | |
R(m, None).map(A.from) | |
} | |
def to[A](implicit A: LabelledGeneric[A]) = new ConverterHelper[A] | |
} | |
object Test extends App { | |
implicit final class ResultOps[A](private val result: Result[String, A]) extends AnyVal { | |
def getAll: NonEmptyVector[String] = | |
result.fold(identity, x ⇒ NonEmptyVector(x.toString)) | |
} | |
case class Address(street: String, zip: Int) | |
case class Person(name: String, addresses: List[Address]) | |
def run(maps: Map[String, Any]*): Unit = maps foreach { m ⇒ | |
val p = FromMap.to[Person].run(m) | |
p.getAll.toVector.foreach(println) | |
println("==========") | |
} | |
val mp0: Map[String, Any] = Map( | |
"name" -> "Tom", | |
"addresses" -> List(Map("street" -> "Jefferson st", "zip" -> 10000): Map[String, Any]) | |
) | |
val mp01: Map[String, Any] = Map( | |
"name" -> "Tom", | |
"addresses" -> Vector(Map("street" -> "Jefferson st", "zip" -> 10000): Map[String, Any]) | |
) | |
val mp1: Map[String, Any] = Map( | |
"names" -> "Tom", | |
"addresses" -> List(Map("street" -> "Jefferson st", "zip" -> 10000): Map[String, Any]) | |
) | |
val mp2: Map[String, Any] = Map( | |
"name" -> "Tom", | |
"address" -> Map("street" -> "Jefferson st", "zip" -> 10000) | |
) | |
val mp3: Map[String, Any] = Map( | |
"name" -> "Tom", | |
"addresses" -> Map("streets" -> "Jefferson st", "zip" -> "10000") | |
) | |
val mp4: Map[String, Any] = Map( | |
"name" -> "Tom", | |
"addresses" -> List(Map("streets" -> "Jefferson st", "zip" -> "10000")) | |
) | |
val mp5: Map[String, Any] = Map( | |
"name" -> "Tom", | |
"addresses" -> List(Map("street" -> "Jefferson st", "zip" -> 10000): Map[String, Any], "foo") | |
) | |
val mp6: Map[String, Any] = Map( | |
"name" -> 't', | |
"addresses" -> List( | |
Map("streets" -> "Jefferson st", "zip" -> "10000"): Map[String, Any], | |
Map("street" -> 'b', "sip" -> 10000): Map[String, Any]) | |
) | |
run(mp0, mp01, mp1, mp2, mp3, mp4, mp5, mp6) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Person(Tom,List(Address(Jefferson st,10000))) | |
========== | |
The value under [addresses] is not a nested list of maps. | |
========== | |
There is no entry named [name] in the map. | |
========== | |
There is no entry named [addresses] in the map. | |
========== | |
The value under [addresses] is not a nested list of maps. | |
========== | |
There is no entry named [addresses.0.street] in the map. | |
The value [10000] under [addresses.0.zip] is not of type [Int] | |
========== | |
The value under [addresses] is not a nested list of maps. | |
========== | |
The value [t] under [name] is not of type [String] | |
There is no entry named [addresses.0.street] in the map. | |
The value [10000] under [addresses.0.zip] is not of type [Int] | |
The value [b] under [addresses.1.street] is not of type [String] | |
There is no entry named [addresses.1.zip] in the map. | |
========== |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, I am trying to test the code on my machine, but I have an error.
Could You tell me, please, Why I have this error?