Search code examples
scalascala-macroscase-classseq

Scala map collection case-class to Map()


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.


Solution

  • 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.