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

2

6

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.

Contortionist answered 8/5, 2014 at 11:30 Comment(5)
Can you post code exemplifying what you would expect in the end? Just method signatures will be fine. Maybe annotate them with library and user.Jadda
Could you give an example of a call to convert2CaseClass and also of parse and what the results should be?Cavity
@DidierDupont I added some possible implementation in the library as requested. The result should be a list of case classes. Seq[String] -> Seq[Seq[String]] -> Seq[case class]. The first transformation is already coded. The second one is the one where I need help.Contortionist
Sorry, I'm still not getting it. Could you just give an "if I call convert2CaseClass A = ... , B = ... , list = .., and caseClass = ..., then the result should be .... Also, I believe the signature you give is just not implementable. Type parameter B appears in the result only, and there is no way to get a B out of thin air.Cavity
@DidierDupont First, thank you for looking to my question! I have added more information, let me know if there are parts still difficult to understand.Contortionist
H
1

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.

Humorist answered 22/3, 2015 at 7:47 Comment(0)
G
0

This answer will not tell you how to do exactly what you want, but it will solve your problem. I think you're overcomplicating things.

What is it you want to do? It appears to me that you're simply looking for a way to serialize and deserialize your case classes - i.e. convert your Scala objects to a generic string format and the generic string format back to Scala objects. Your serialization step presently is something you seem to already have defined, and you're asking about how to do the deserialization.

There are a few serialization/deserialization options available for Scala. You do not have to hack away with Shapeless or Scalaz to do it yourself. Try to take a look at these solutions:

  1. Java serialization/deserialization. The regular serialization/deserialization facilities provided by the Java environment. Requires explicit casting and gives you no control over the serialization format, but it's built in and doesn't require much work to implement.
  2. JSON serialization: there are many libraries that provide JSON generation and parsing for Java. Take a look at play-json, spray-json and Argonaut, for example.
  3. The Scala Pickling library is a more general library for serialization/deserialization. Out of the box it comes with some binary and some JSON format, but you can create your own formats.

Out of these solutions, at least play-json and Scala Pickling use macros to generate serializers and deserializers for you at compile time. That means that they should both be typesafe and performant.

Gules answered 15/5, 2014 at 15:11 Comment(6)
You are right about what I want to do: I want to deserialize strings to case class. But I want to do make it automagick, I don't want the users of my lib to need to write their own deserializer each time. By leveraging Shapeless, I will be able to get the Type of the case class, I can write some implicit converter to deserialize most common types (int, double, long...) and offer the user to provide its own converted (date, time, whatever). For that I really need shapeless. Because I have a specific format to deserialize, I can t use existing library for Json you are listing here.Contortionist
I think I misunderstood the question somewhat - but still, Scala Pickling should generally be in the direction of what you want. Check it out.Gules
I look for some example of use of Scala Pickling, it seems interesting at first, but I have found that you may need custom deserializer for your custom format for each type you want to use (means each case class). Moreover the documentation is very thin.Contortionist
As you have been the only one to be nice enough to try to answer, I gave you the bounty points, but I still can t use your answer. Anyway, thanks for having tried :)Contortionist
Thanks! If you want to implement something like this yourself, please look at the macros used in Play's JSON support. I have a hunch that what you want is only possible in the general case using macros, but I can't prove it.Gules
I think too that Macro would work, I know that Shapeless use them. However it seems to be another world, when you see your code as a tree to parse. It may require lots of work, that's why I am still hopping someone give me some advice, but if not... I imagine I would search for good tutorial and read carefully the source code of Play JSon support...Contortionist

© 2022 - 2024 — McMap. All rights reserved.