3

Lets say I have

case class Sample(i:Int, b:Boolean) 

and

Map[String, String]("i" => "1", "b" => "false")

What is most concise way to instantiate any case class(if all fields are in map), signature like this:

 get[T](map:[String, String]) : T

Probably shapeless can help acomplish this task, but I am almost unfamiliar with it. Thanks!

4
  • Sounds very similar to parsing JSON... Commented Dec 10, 2017 at 20:26
  • Yes, Hosam. It is part of CSV parser solution. Commented Dec 12, 2017 at 18:54
  • Do you need to rewrite it? There are multiple CSV parsing libraries for both Java (e.g. Apache Commons CSV) and Scala (e.g. scala-csv-parser) Commented Dec 12, 2017 at 19:12
  • At least it was fun:) But yes, I need custom modifications in parsing logic, so existed libs don't fit my needs. Commented Dec 12, 2017 at 19:27

3 Answers 3

3

Firstly you can transform Map[String, String] into Map[Symbol, Any] and then use type classes shapeless.ops.maps.FromMap (or extention method .toRecord) and LabelledGeneric:

import shapeless.LabelledGeneric
import shapeless.record.Record
import shapeless.syntax.std.maps._

case class Sample(i: Int, b: Boolean)
val rec = Map('i -> 1, 'b -> false).toRecord[Record.`'i -> Int, 'b -> Boolean`.T].get
LabelledGeneric[Sample].from(rec) //Sample(1,false)
Sign up to request clarification or add additional context in comments.

Comments

1

If not using Shapeless is an option for you, you could do it in a simple way without it. Here is a sample implementation where I use the Try Monad and a pattern matching to transform the Map into your case class:

  (Try("1".toInt), Try("false".toBoolean)) match {
    case (Success(intVal), Success(boolVal)) =>
      Sample(intVal, boolVal)
    case _ => // Log and ignore the values
  }

Of course this is a bit verbose than the Shapeless version, but if you do not want to use a full library just for this simple use case, you could always do it using Scala library!

Comments

0

guys. Probably I wasn't too concise. What I really need is Generic parser from Map of Strings to case class. Thanks for pointing out to LabelledGeneric. Here is solution:

    import shapeless.labelled.{FieldType, field}
    import shapeless.{::, HList, HNil, LabelledGeneric, Lazy, Witness}

    object FieldParser {
  trait ValParser[A] {
    def parse(str:String): A
  }

  trait FieldParser[A] {
    def parse: A
  }

  type FT[A, B] = FieldType[A, B]
  type ListField[K <: Symbol, A] = FieldType[K, List[A]]
  type FP[A] = FieldParser[A]
  type Args = (List[String],Map[String, Int])
  type Named[K <: Symbol] = Witness.Aux[K]

  private def create[A](thunk: A): FP[A] = {
    new FP[A] {
      def parse: A = thunk
    }
  }

  def apply[A](implicit st: Lazy[FP[A]]): FP[A] = st.value

  implicit def genericParser[A, R <: HList](implicit generic: LabelledGeneric.Aux[A, R], parser: Lazy[FP[R]], args:Args): FP[A] = {
    create(generic.from(parser.value.parse))
  }

  implicit def hlistParser[K <: Symbol, H, T <: HList](implicit hParser: Lazy[FP[FT[K, H]]], tParser: FP[T]): FP[FT[K, H] :: T] = {
    create {
      val hv = hParser.value.parse
      val tv = tParser.parse
      hv :: tv
    }
  }

  implicit def standardTypeParser[K <: Symbol, V:ValParser](implicit named: Named[K], args:Args): FP[FieldType[K, V]] = {
    create(field[K](implicitly[ValParser[V]].parse(findArg)))
  }

  implicit def optionParser[V](implicit valParser:ValParser[V]): ValParser[Option[V]] = new ValParser[Option[V]]{
    def parse(str:String):Option[V] = {
      str.isEmpty match {
        case true => None
        case false => Some(valParser.parse(str))
      }
    }
  }

  implicit def listParser[V](implicit valParser:ValParser[V]): ValParser[List[V]] = new ValParser[List[V]]{
    def parse(str:String):List[V] = {
      str.isEmpty match {
        case true => Nil
        case false => str.split(",").map(valParser.parse).toList
      }
    }
  }

  implicit def doubleParser: ValParser[Double] = new ValParser[Double]{
    def parse(str:String):Double = str.toDouble
  }

  implicit def intParser: ValParser[Int] = new ValParser[Int]{
    def parse(str:String):Int = str.toInt
  }

  implicit def strParser: ValParser[String] = new ValParser[String]{
    def parse(str:String):String = str
  }

  implicit def boolParser: ValParser[Boolean] = new ValParser[Boolean]{
    def parse(str:String):Boolean = str.toBoolean
  }

  implicit val hnilParser: FP[HNil] = create(HNil)

  private def findArg[K <: Symbol](implicit args:Args, named: Named[K]): String = {
    val name = named.value.name
    val index = args._2(name)
    args._1(index)
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.