7
votes

Map[String,Object] from a DB (or Key Value store) to a Shapeless Extensible Record

Example:

Lets say I have a map

val fromDB: Map[String, Any] = Map("name" -> "John", "age" -> 25)

Knowing that the field "name" should be a string and the field "age" should be an integer, how would I convert that to a Shapeless Extensible Record as below?

val user = ("name" ->> "John") :: ("age"  ->> 25) :: HNil

My end goal is to create an object as below that can convert the Map using the function "fromDB" using the fields.

object User {

  object name extends FieldOf[String]
  object age extends FieldOf[Int]

  def fromDB(data: Map[String,Any]) = {
    //TODO
  }
}

I'm open to other suggestion and ways of doing this as well. Thanks.

1

1 Answers

10
votes

You can use TypeCase extractors to write this pretty cleanly:

import shapeless._, syntax.singleton._

val StrCase = TypeCase[String]
val IntCase = TypeCase[Int]

def toUserRecord(m: Map[String, Any]) = for {
   StrCase(name) <- m.get("name")
   IntCase(age)  <- m.get("age")
} yield ("name" ->> name) :: ("age" ->> age) :: HNil

Or you could make the casts a little more explicit:

import syntax.typeable._

def toUserRecord(m: Map[String, Any]) = for {
  name <- m.get("name").flatMap(_.cast[String])
  age  <- m.get("age").flatMap(_.cast[Int])
} yield ("name" ->> name) :: ("age" ->> age) :: HNil

Note that I'm returning an Option[LongRecordTypeHere] to keep this type-safeā€”if the keys don't exist in the map or the values can't be cast to the right type, we'll get a None.