1
votes

I have Map[String, Any], for example from JSON deserializer:

def map: Map[String, Any] = Map("hello" -> Map("world" -> "value"))

And I want to get Option of nested key "hello"."world":

val value = map.get("hello").flatMap {
  case m: Map[String, String] => m.get("value")
  case _ => None
}

assert(value == Some("value"))

But this solution is not type safe - it emits warning 'is unchecked since it is eliminated by erasure' and will fail on wrong value type.

How to accomplish this the safe way? Returning value if nested object is a map of valid/compatible type containing key "world" or None otherwise?

edit: This is not about Manifest[] or TypeTag[]. I know that exact Map type is not accessible in runtime but want better solution than casting keys somehow (is this safe btw?) and doing isinstanceof for values.

4
Your code doesn't work. Inside flatMap you already have an Any not an Option[Any]. But otherwise it's a valid question. I think the answer is you can't do this.Daniel Darabos
@DanielDarabos m.get returns Option[String] so code is validSergey Alaev
@Suma I don't think this is a duplicate. You cannot solve the problem with TypeTagsArcheg
m.get returns Option[Any]. But that's not the point. flatMap let's you operate on the inside of the Option[Any]. So you need case m: Map[String, String] and no case None. Just try it and you see.Daniel Darabos
@DanielDarabos There is a solution, but it's overly ugly and unpractical. You could match Map[_,_], iterate over keys and check their type. I do not have any other ideas unfortunatelyArcheg

4 Answers

2
votes

For this case

val value = map.get("hello").flatMap {
  case m: Map[String @unchecked, _] => m.get("value")
  case _ => None
}

is safe: if m's key type isn't String, get will just return None! Another thing you could do is to work with an actual JSON AST, since you don't really have a Map[String, Any]:

sealed trait JValue
...
case class JMap(underlying: Map[String, JValue]) {
  def get(key: String) = underlying.get(key)
}

Now you can easily define what you want without casts or even potential unsafety:

val map: JMap = ...
val value = map.get("hello").flatMap {
  case m: JMap => m.get("value")
  case _ => None
}
1
votes

Just for theory reason. It's possible to do this:

 val value = map.get("hello").map {
    case m: Map[_, _] => m collectFirst {case ("world", v) => v}
    case _ => None
  }

But it's overly complicated, and unpractical. Even worse, it searches the inner Map in O(n), when maps are supposed to be searched in O(1).

I don't think you can do it any other way, and I would not recommend even this one. There could be though solutions using the same idea, but simpler - unfortunately I could not think of any

0
votes

There is a elegant way to solve this using shapeless.

1) Declare a TypeCase

import shapeless._
val mapType: TypeCase[Map[String, String]] = TypeCase[Map[String, String]]

2) use it on the pattern matching to avoid the Type Erasure

val value = Map("hello" -> Map("world" -> "value")).get("hello").flatMap {
  case mapType(m) => m.get("world")
  case _ => None
} 
assert(value == Some("value"))
-1
votes

Try this:

val v = for {
  nestedMap <- map("Hello")
  value <- nestedMap("world")
} yield value