Search code examples
scalafunctional-programmingtransformation

How to convert complex list of objects into the map in Scala


I'm looking for transforming my case List of objects into the Map. This

input class looks like this

case class MyData(
    source: String,
    dataMap: Map[String, String]
)

Resultant class post-transformation

case class LiverampMappingV2(
    dataMapping: Map[String, Map[String, Seq[String]]]
)

For example, following input data -->

val data = List(
      new myData("abc.com", Map("key1" -> "value1", "key2" -> "value2")),
      new myData("abc.com", Map("key1" -> "value11", "key3" -> "value3")),
      new myData("pqr.com", Map("key1" -> "value111", "key3" -> "value33"))
    )

will be assembled as follows

Map(
       key1 -> Map("abc.com" -> List("value1", "value11"), "pqr.com" -> List("value111")),
       key2 -> Map("abc.com" -> List("value2")),
       key3 -> Map("abc.com" -> List("value13"), "pqr.com" -> List("value33"))
   )

I tried a couple of options using combination of groupBy and groupMap followed by flatten but being newbie to scala and functional programming, I can't reach to the final solution.

Any help or guidance is appreciated.


Solution

  • I also can recommend another approach if you do not have cats in scope :D Although that's pretty verbose and a little complicated. And that's because you need to fold on each iteration over your first original "data". But I'll try to explain:

    type MP = Map[String, List[String]] 
    // just for shortening the code, you can put a better name on it
    
    data.foldLeft[Map[String, MP]](Map.empty)(
        (agg, newData) => {
          val source = newData.source
          newData.dataMap.foldLeft(agg) {
            case (updatingAggregator, (key, value)) =>
              updatingAggregator.updatedWith(key) {
                case Some(existingMap) =>
                  Some(existingMap.updatedWith(source) {
                    case Some(existingList) => Some(value :: existingList)
                    case None => Some(value :: Nil)
                  })
                case None =>
                  Some(Map(source -> (value :: Nil)))
              }
          }
        }
      )
    

    Basically what we're doing here is folding over the data, with a zero value of empty Map of String to MP (which is the resulting type we want from the algorithm, and from folding). On each iteration you have the source (val source = ...). And you also have a dataMap, which you need to fold again (:D), and update the previously created agg value inside it, using the given source, key and value. Now in the updatedWith method, you basically look for the source inside the updatingAggregator, and if it already has it, you just prepend the value, and if not, you just make it. Let me know if the description was not clear enough.