0

I have a simple method to retrieve a nested key from a hashmap. I need to pattern match on Map[String,Any] so that I can keep iterating into the nested data until I get to a flat value:

def get(map: Map[String, Any], key: String): Any = {
   var fields: mutable.Seq[String] = key.split('.')
   var currentKey: String = fields.head
   var currentValue: Any = map

   while (fields.nonEmpty && currentValue.isInstanceOf[Map[String, Any]]) {
     currentKey = fields.head
     fields = fields.drop(1)
     currentValue match {
       case m: Map[String, Any] => currentValue = m.getOrElse(currentKey, None)
       case _                   =>
     }
   }
   if (fields.nonEmpty) None else currentValue
 }

It works when I use it only within scala, but if it gets called from java, I get the error non-variable type argument String in type scala.collection.immutable.Map[String,Any].

I've seen some other solutions that require you to refactor code and wrap the map in a case class, but that would be very disruptive to all the code that relies on this method. Is there any simpler fix?

4
  • Accidentally tagged as js. Thanks for the edit! Commented Apr 30, 2020 at 20:33
  • How are you converting the Java Maps to Scala Maps when you call it from Java? Commented Apr 30, 2020 at 21:15
  • That's something that I'm still working out, thinking about using HashMap<String, Object>. But for now I am passing it as json and deserializing it myself before using get() Commented Apr 30, 2020 at 21:18
  • In which case the error is likely to be in the code that creates the Map, not in this method. Don't forget the compiler warnings that tell you that the type matching is not working correctly because of type erasure. Commented Apr 30, 2020 at 21:25

1 Answer 1

1

You cannot pattern match on Map[String,Any] because of type erasure. The compiler will have warned of this. This code is just matching on Map[_,_] so it will succeed with any key type, not just String.

So the method is inherently buggy and it appears that calling from Java is exposing bugs that did not emerge when using Scala.

Since you are not using this from Java yet, I would switch to a typesafe implementation for the Java code and then migrate the legacy code to this version as quickly as possible. While this may be disruptive, it would be fixing a design error that introduced buggy code, so it should be done sooner rather than later. (Whenever you see Any used as a value type it is likely that the design went wrong at some point)

The typesafe version is not that difficult, here is an outline implementation:

class MyMap[T] {
  trait MapType
  case class Value(value: T) extends MapType
  case class NestedMap(map: Map[String, MapType]) extends MapType

  def get(map: Map[String, MapType], key: String): Option[T] = {
    def loop(fields: List[String], map: Map[String, MapType]): Option[T] =
      fields match {
        case Nil =>
          None
        case field :: rest =>
          map.get(field).flatMap{
            case Value(res) => Some(res)
            case NestedMap(m) => loop(rest, m)
          }
      }

    loop(key.split('.').toList, map)
  }
}

In reality MyMap should actually hold the Map data rather than passing it in to get, and there would be methods for safely building nested maps.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.