Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting a List to a Case Class

As an exercise, I am trying to see if I can take a List[Any] and "cast" it into a case class using shapeless.

A very basic example of what I am trying to achieve:

case class Foo(i: Int, j: String)
val foo: Option[Foo] = fromListToCaseClass[Foo]( List(1:Any, "hi":Any) ) 

Here is how I am shaping my solution (this can be quite off):

def fromListToCaseClass[CC <: Product](a: List[Any]): Option[CC] = a.toHList[???].map( x => Generic[CC].from(x) )

Here is my reasoning:

I know that you can go from a case class to an HList[T] (CC -> HList[T]); where T is the type of the HList. I also know that you can create an HList from a list (list -> Option[HList]) as long as you know the type of the HList. Finally I know that you can go from an HList to a case class (HList -> CC).

CC -> HList[T]
list -> Option[HList[T]] -> Option[CC]

I am wondering if this makes sense or if I am way off here. Can we make this work? Any other suggestions? Thanks!

like image 779
marios Avatar asked Oct 26 '25 20:10

marios


2 Answers

This can be done very straightforwardly using shapeless's Generic and FromTraversable type classes,

import scala.collection.GenTraversable
import shapeless._, ops.traversable.FromTraversable

class FromListToCaseClass[T] {
  def apply[R <: HList](l: GenTraversable[_])
    (implicit gen: Generic.Aux[T, R], tl: FromTraversable[R]): Option[T] =
      tl(l).map(gen.from)
}
def fromListToCaseClass[T] = new FromListToCaseClass[T]

(There's some accidental complexity here due to Scala's awkwardness when it comes to mixing explicit and inferred type parameters: we want to specify T explicitly, but have R inferred for us).

Sample REPL session ...

scala> case class Foo(i: Int, j: String)
defined class Foo

scala> fromListToCaseClass[Foo](List(23, "foo"))
res0: Option[Foo] = Some(Foo(23,foo))

scala> fromListToCaseClass[Foo](List(23, false))
res1: Option[Foo] = None
like image 118
Miles Sabin Avatar answered Oct 28 '25 22:10

Miles Sabin


You can do it with shapeless the following way:

import shapeless._

trait Creator[A] { def apply(list:List[Any]): Option[A] }

object Creator {
  def as[A](list: List[Any])(implicit c: Creator[A]): Option[A] = c(list)

  def instance[A](parse: List[Any] => Option[A]): Creator[A] = new Creator[A] {
    def apply(list:List[Any]): Option[A] = parse(list)
  }

  def arbitraryCreate[A] = instance(list => list.headOption.map(_.asInstanceOf[A]))

  implicit val stringCreate = arbitraryCreate[String]
  implicit val intCreate = arbitraryCreate[Int]
  implicit val hnilCreate = instance(s => if (s.isEmpty) Some(HNil) else None)

  implicit def hconsCreate[H: Creator, T <: HList: Creator]: Creator[H :: T] =
instance {
  case Nil => None
  case list => for {
    h <- as[H](list)
    t <- as[T](list.tail)
  } yield h :: t
}

implicit def caseClassCreate[C, R <: HList](
  implicit gen: Generic.Aux[C, R], 
  rc: Creator[R]): Creator[C] = 
    instance(s => rc(s).map(gen.from))
}

And

val foo:Option[Foo] = Creator.as[Foo](List(1, "hi"))
like image 43
Nyavro Avatar answered Oct 29 '25 00:10

Nyavro