How to convert values of a case class into Seq?

Viewed 1422

I am new to Scala and I am having to provide values extracted from an object/case class into a Seq. I was wondering whether there would be any generic way of extracting values of an object into Seq of those values in order?

Convert the following:

case class Customer(name: Option[String], age: Int)
val customer = Customer(Some("John"), 24)

into:

val values = Seq("John", 24)
3 Answers

case class extends Product class and it provides such method:

case class Person(age:Int, name:String, lastName:Option[String])

def seq(p:Product) = p.productIterator.toList

val s:Seq[Any] = seq(Person(100, "Albert", Some("Einstain")))
println(s) //List(100, Albert, Some(Einstain))

https://scalafiddle.io/sf/oD7qk8u/0

Problem is that you will get untyped list/array from it. Most of the time it is not optimal way of doing things, and you should always prefer statically typed solutions.

Scala 3 (Dotty) might give us HList out-of-the-box which is a way of getting product's values without loosing type information. Given val picard = Customer(Some("Picard"), 75) consider the difference between

val l: List[Any] = picard.productIterator.toList
l(1)
// val res0: Any = 75

and

val hl: (Option[String], Int) = Tuple.fromProductTyped(picard)
hl(1)
// val res1: Int = 75

Note how res1 did not loose type information.

Informally, it might help to think of an HList as making a case class more generic by dropping its name whilst retaining its fields, for example, whilst Person and Robot are two separate models

Robot(name: Option[String], age: Int)
Person(name: Option[String], age: Int)

they could both represented by a common "HList" that looks something like

(_: Option[String], _: Int) // I dropped the names

If it's enough for you to have Seq[Any] you can use productIterator approach proposed by @Scalway. If I understood correctly you want also to unpack Option fields. But you haven't specified what to do with None case like Customer(None, 24).

val values: Seq[Any] = customer.productIterator.map {
  case Some(x) => x
  case x => x
}.toSeq // List(John, 24)

Statically typed solution would be to use heterogeneous collection e.g. HList

class Default[A](val value: A)
object Default {
  implicit val int: Default[Int] = new Default(0)
  implicit val string: Default[String] = new Default("")
  //...
}

trait LowPriorityUnpackOption extends Poly1 {
  implicit def default[A]: Case.Aux[A, A] = at(identity)
}

object unpackOption extends LowPriorityUnpackOption {
  implicit def option[A](implicit default: Default[A]): Case.Aux[Option[A], A] = at {
    case Some(a) => a
    case None => default.value
  }
}

val values: String :: Int :: HNil =
  Generic[Customer].to(customer).map(unpackOption) // John :: 24 :: HNil

Generally it would be better to work with Option monadically rather than to unpack them.

Related