Last active
June 25, 2017 09:39
-
-
Save limansky/7c91a3deaa25fc0ec7339ea191d4a54a to your computer and use it in GitHub Desktop.
Read Map[String, String] to case class
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
package me.limansky | |
import cats.Monoid | |
import shapeless.labelled.{FieldType, field} | |
import shapeless.{::, HList, HNil, LabelledGeneric, Lazy, Witness} | |
trait MapReader[T] { | |
def read(m: Map[String, String]): T | |
} | |
object MapReader { | |
trait ValueDecoder[T] { | |
def decode(s: String): T | |
} | |
implicit val stringDecoder: ValueDecoder[String] = new ValueDecoder[String] { | |
override def decode(s: String): String = s | |
} | |
implicit val intDecoder: ValueDecoder[Int] = new ValueDecoder[Int] { | |
override def decode(s: String): Int = s.toInt | |
} | |
def apply[T](implicit ev: MapReader[T]): MapReader[T] = ev | |
implicit val hnilReader: MapReader[HNil] = new MapReader[HNil] { | |
override def read(m: Map[String, String]) = HNil | |
} | |
implicit def hconsValue[K <: Symbol, H, T <: HList](implicit | |
witness: Witness.Aux[K], | |
hDecoder: ValueDecoder[H], | |
hMonoid: Monoid[H], | |
tReader: MapReader[T] | |
): MapReader[FieldType[K, H] :: T] = new MapReader[FieldType[K, H] :: T] { | |
override def read(m: Map[String, String]) = { | |
val name = witness.value.name | |
field[K](m.get(name).map(hDecoder.decode).getOrElse(hMonoid.empty)) :: tReader.read(m) | |
} | |
} | |
implicit def hconsProduct[K <: Symbol, H, T <: HList](implicit | |
witness: Witness.Aux[K], | |
hReader: Lazy[MapReader[H]], | |
tReader: MapReader[T] | |
): MapReader[FieldType[K, H] :: T] = new MapReader[FieldType[K, H] :: T] { | |
override def read(m: Map[String, String]) = { | |
field[K](hReader.value.read(m)) :: tReader.read(m) | |
} | |
} | |
implicit def genericReader[A, R](implicit | |
gen: LabelledGeneric.Aux[A, R], | |
mapReader: Lazy[MapReader[R]] | |
): MapReader[A] = new MapReader[A] { | |
override def read(m: Map[String, String]) = gen.from(mapReader.value.read(m)) | |
} | |
} |
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
package me.limansky | |
import org.scalatest.{FlatSpec, Matchers} | |
class MapReaderTest extends FlatSpec with Matchers { | |
import cats.instances.all._ | |
"MapReader" should "read simple case classes" in { | |
case class Test(a: String, b: Int) | |
MapReader[Test].read(Map("a" -> "foo", "b" -> "7")) shouldEqual Test("foo", 7) | |
} | |
it should "support nested classes" in { | |
case class Inner(one: Int, two: Int) | |
case class Outer(three: String, inner: Inner) | |
MapReader[Outer].read(Map("one" -> "1", "two" -> "2", "three" -> "3")) shouldEqual Outer("3", Inner(1, 2)) | |
} | |
it should "init missing data" in { | |
case class Inner(one: Int, two: Int) | |
case class Outer(three: String, inner: Inner) | |
MapReader[Outer].read(Map("one" -> "42")) shouldEqual Outer("", Inner(42, 0)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment