Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert a Seq[String] to a case class in a typesafe way

I have written a parser which transforms a String to a Seq[String] following some rules. This will be used in a library.

I am trying to transform this Seq[String] to a case class. The case class would be provided by the user (so there is no way to guess what it will be).

I have thought to shapeless library because it seems to implement the good features and it seems mature, but I have no idea to how to proceed.

I have found this question with an interesting answer but I don't find how to transform it for my needs. Indeed, in the answer there is only one type to parse (String), and the library iterates inside the String itself. It probably requires a deep change in the way things are done, and I have no clue how.

Moreover, if possible, I want to make this process as easy as possible for the user of my library. So, if possible, unlike the answer in link above, the HList type would be guess from the case class itself (however according to my search, it seems the compiler needs this information).

I am a bit new to the type system and all these beautiful things, if anyone is able to give me an advice on how to do, I would be very happy!

Kind Regards

--- EDIT ---

As ziggystar requested, here is some possible of the needed signature:

//Let's say we are just parsing a CSV.

@onUserSide
case class UserClass(i:Int, j:Int, s:String)
val list = Seq("1,2,toto", "3,4,titi")

// User transforms his case class to a function with something like:
val f = UserClass.curried

// The function created in 1/ is injected in the parser
val parser = new Parser(f)

// The Strings to convert to case classes are provided as an argument to the parse() method.
val finalResult:Seq[UserClass] = parser.parse(list) 
// The transfomation is done in two steps inside the parse() method:
// 1/ first we have: val list = Seq("1,2,toto", "3,4,titi")
// 2/ then we have a call to internalParserImplementedSomewhereElse(list)
//    val parseResult is now equal to Seq(Seq("1", "2", "toto"), Seq("3","4", "titi"))
// 3/ finally Shapeless do its magick trick and we have Seq(UserClass(1,2,"toto"), UserClass(3,4,"titi))



@insideTheLibrary
class Parser[A](function:A) {

 //The internal parser takes each String provided through argument of the method and transforms each String to a Seq[String]. So the Seq[String] provided is changed to Seq[Seq[String]]. 
 private def internalParserImplementedSomewhereElse(l:Seq[String]): Seq[Seq[String]] = {
  ...
 }

 /*
 * Class A and B are both related to the case class provided by the user: 
 * - A is the type of the case class as a function, 
 * - B is the type of the original case class (can be guessed from type A).
 */
 private def convert2CaseClass[B](list:Seq[String]): B {
    //do  something with Shapeless
    //I don't know what to put inside ???
 }

 def parse(l:Seq[String]){
   val parseResult:Seq[Seq[String]] = internalParserImplementedSomewhereElse(l:Seq[String])
   val finalResult = result.map(convert2CaseClass)
   finalResult // it is a Seq[CaseClassProvidedByUser]       
 }
}    

Inside the library some implicit would be available to convert the String to the correct type as they are guessed by Shapeless (similar to the answered proposed in the link above). Like string.toInt, string.ToDouble, and so on...

May be there are other way to design it. It's just what I have in mind after playing with Shapeless few hours.

like image 622
pommedeterresautee Avatar asked May 08 '14 11:05

pommedeterresautee


1 Answers

This uses a very simple library called product-collecions

import com.github.marklister.collections.io._
case class UserClass(i:Int, j:Int, s:String)

val csv = Seq("1,2,toto", "3,4,titi").mkString("\n")
csv: String =
1,2,toto
3,4,titi

CsvParser(UserClass).parse(new java.io.StringReader(csv))
res28: Seq[UserClass] = List(UserClass(1,2,toto), UserClass(3,4,titi))

And to serialize the other way:

scala> res28.csvIterator.toList
res30: List[String] = List(1,2,"toto", 3,4,"titi")

product-collections is orientated towards csv and a java.io.Reader, hence the shims above.

like image 76
Mark Lister Avatar answered Oct 14 '22 08:10

Mark Lister