2
votes

I have some nested maps like:

val map1 = Map("key1"->1, "key2"->Map("x"->List(1,2)))
val map2 = Map("key3"->3, "key2"->Map("y"->List(3,4)))

And I want to merge them to obtain the result map like;

val res = Map("key1"->1, "key2"->Map("x"->List(1,2), "y"->List(3,4)), "key3"->3)

So nested maps should also be merged. The type of the maps and nested maps can be assumed as Map[String, Any]. It's considered an exception if the two maps have conflict keys (eg. values of the same key are different, except that the value is a nested map).

Is there some elegant solution to this? Thanks!

2
what if the same key (like "key2" in the example) points to a value that's not a map? E.g. key1 -> 1 in map1 and key1 -> 2 in map2. What if one value is a map and the other isn't? You have a code (bad) smell here by the wayGiovanni Caporaletti
This data structure is, well... ugly Oo. I can't find a decent solution. I'd try to use foldLeft, but with both Int and Map as values in your higher map...Agemen
In fact that's some deserialized json document.darkjh

2 Answers

3
votes
type MapType = Map[String, Any]

def merge(map1 : MapType, map2 : MapType) = (map1.keySet ++ map2.keySet)
  .map(key => key -> mergeValues(map1.get(key), map2.get(key)))
  .toMap

private def mergeValues(o1 : Option[Any], o2 : Option[Any]) = (o1, o2) match {
  case (Some(v1), Some(v2)) => merge(v1.asInstanceOf[MapType], v2.asInstanceOf[MapType])
  case _ => (o1 orElse o2).get
}

Improved version of norbert-radyk with :

  • Multiple nested level support suggested by darkjh
  • Scala 2.11 compatibility to avoid this error : non-variable type argument String in type pattern scala.collection.immutable.Map[String,Any] (the underlying of MapType) is unchecked since it is eliminated by erasure case (Some(v1 : MapType), Some(v2 : MapType)) => v1 ++ v2
2
votes

How about the following:

type MapType = Map[String, Any]

def merge(map1 : MapType, map2 : MapType) = (map1.keySet ++ map2.keySet)
  .map(key => key -> mergeValues(map1.get(key), map2.get(key)))
  .toMap

private def mergeValues(o1 : Option[Any], o2 : Option[Any]) = (o1, o2) match {
  case (Some(v1 : MapType), Some(v2 : MapType)) => v1 ++ v2
  case _ => (o1 orElse o2).get
}

You can modify the mergeValues function to support additional cases (i.e. situation when the same key points to 2 values which are not maps - at the moment will return the first of the values).