0

I have 2 case-classes:

case class OutlierPortal(portal: String, timeData: Seq[OutlierPortalTimeSeriesData])

and

case class OutlierPortalTimeSeriesData(period: Timestamp, totalAmount: Double, isOutlier: Int)

or respectively a Seq[OutlierPortal]

What I want to perform is similar to Scala Macros: Making a Map out of fields of a class in Scala, but I want to map a sequence of a (nested) case-classes to Seq[Map[String, Any]].

However, new to scala I fear a bit the proposed idea of macros. Is there a "simpler" way to map this Sequence of Seq[OutlierPortal] to Seq[Map[String, Any]]

Or would you recommend to start using macros even though a beginner in scala? For me a one-way conversion (case-class -> map) is enough.

Community
  • 1
  • 1
Georg Heiler
  • 16,916
  • 36
  • 162
  • 292
  • To be clear, you're looking to avoid hard-coding the strings? – Dan Getz May 16 '16 at 15:37
  • Flexible keys would be nice but is not mandatory – Georg Heiler May 16 '16 at 15:41
  • You want the `value` of type `Any` to contain the fields value? – Yuval Itzchakov May 16 '16 at 16:09
  • Also, have you seen the answers to this question? Might be a duplicate of yours: http://stackoverflow.com/questions/1226555/case-class-to-map-in-scala – Dan Getz May 16 '16 at 16:20
  • @danGetz is this method http://stackoverflow.com/questions/1226555/case-class-to-map-in-scala preferable over macros? what is the advantage of macros over this short-hand solution? – Georg Heiler May 16 '16 at 18:12
  • @geoHeil That other method uses Java reflection. Macros generate code at compile time, so should have better performance than reflection. With either of those solutions, you only need to write them once, then you can use them with as many case classes as you like. – Dan Getz May 16 '16 at 18:26

1 Answers1

4

If you're looking to avoid fancy tricks, and you don't have too many total classes to write this for, you can just write the methods to create the maps yourself. I'd suggest adding methods named something like toMap to your case classes. OutlierPortalTimeSeriesData's is simple if you use the Map() constructor:

case class OutlierPortalTimeSeriesData(period: Timestamp, totalAmount: Double, isOutlier: Int) {
  def toMap: Map[String, Any] = Map(
    "period" -> period,
    "totalAmount" -> totalAmount,
    "isOutlier" -> isOutlier)
}

I suppose there's some duplication there, but at least if you ever have a reason to change the string values but not the variable names, you have the flexibility to do that.

To take a sequence of something you can call toMap on, and turn it into a Seq[Map[String, Any]], just use map:

mySeq.map { _.toMap }

We can use this both to write OutlierPortal's toMap:

case class OutlierPortal(portal: String, timeData: Seq[OutlierPortalTimeSeriesData]) {
  def toMap: Map[String, Any] = Map(
    "portal" -> portal,
    "timeData" -> timeData.map { _.toMap })
}

and then again to convert a Seq[OutlierPortal] to a Seq[Map[String, Any]].

Depending on how you're using these objects and methods, you might want to define a trait that distinguishes classes with this method, and have your case classes extend it:

trait HasToMap { def toMap: Map[String, Any] }
case class Blah( /* ... */ ) extends HasToMap {
  def toMap: /* ... */ }
}

This would let you take a value that you know you can convert to Map[String, Any] (or a sequence of them, etc.) in a method that otherwise doesn't care which particular type it is.

Dan Getz
  • 8,774
  • 6
  • 30
  • 64
  • Implementing this solution for now I get the following error: `def mapOutliersForJobserver(outliers: Future[Seq[OutlierPortal]]): Future[Seq[Map[String, Any]]] = { outliers.map( _.toMap) }` error: `Future[Map[Nothing, Nothing]] does not conform to type Future[Seq[Map[String, Any]]]` What is the problem here? ist it the Seq[T]? – Georg Heiler May 16 '16 at 18:51
  • @geoHeil You're trying to treat a `Future[Seq[T]]` the same as a `Seq[T]`. They're different, though, just as a `Seq[Seq[T]]` would be. – Dan Getz May 16 '16 at 18:53
  • @geoHeil so for example you could use two nested calls to `map` like `{outliers.map { _.map { _.toMap } }`. There must be a cleaner way to do it though. – Dan Getz May 16 '16 at 18:57