【问题标题】:How to match typed nested map?如何匹配类型化的嵌套地图?
【发布时间】:2017-09-15 18:08:59
【问题描述】:

我有Map[String, Any],例如来自 JSON 反序列化器:

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

我想得到嵌套键"hello"."world"Option

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

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

但是这个解决方案不是类型安全的——它会发出警告“因为它被擦除而被消除,所以它是未经检查的”,并且会在错误的值类型上失败。

如何以安全的方式做到这一点?如果嵌套对象是包含键 "world"None 的有效/兼容类型的映射,则返回值?

编辑:这与Manifest[]TypeTag[] 无关。我知道在运行时无法访问确切的 Map 类型,但想要比以某种方式强制转换键更好的解决方案(顺便说一句,这安全吗?)并为值执行 isinstanceof

【问题讨论】:

  • 您的代码不起作用。在flatMap 中,您已经拥有Any 而不是Option[Any]。但除此之外,这是一个有效的问题。我认为答案是你不能这样做。
  • @DanielDarabos m.get 返回Option[String] 所以代码是有效的
  • @Suma 我不认为这是重复的。你不能用TypeTags解决问题
  • m.get 返回Option[Any]。但这不是重点。 flatMap 让您在Option[Any]内部 上进行操作。所以你需要case m: Map[String, String] 而不需要case None。试一试,你就会看到。
  • @DanielDarabos 有一个解决方案,但它过于丑陋且不实用。你可以匹配Map[_,_],遍历键并检查它们的类型。不幸的是,我没有任何其他想法

标签: scala pattern-matching


【解决方案1】:

对于这种情况

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

是安全的:如果m 的密钥类型不是Stringget 将只返回None!您可以做的另一件事是使用实际的 JSON AST,因为您真的没有Map[String, Any]

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

现在您可以轻松定义您想要的内容,而无需强制转换甚至潜在的不安全:

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

【讨论】:

    【解决方案2】:

    仅出于理论原因。可以这样做:

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

    但它过于复杂且不实用。更糟糕的是,它在 O(n) 中搜索内部 Map,而地图应该在 O(1) 中搜索。

    我认为你不能以任何其他方式做到这一点,我什至不会推荐这种方式。虽然可能有使用相同想法的解决方案,但更简单 - 不幸的是我想不出任何解决方案

    【讨论】:

      【解决方案3】:

      有一种优雅的方法可以使用 shapeless 来解决这个问题。

      1) 声明一个 TypeCase

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

      2) 在模式匹配上使用它来避免类型擦除

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

      【讨论】:

        【解决方案4】:

        试试这个:

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

        【讨论】:

        • 它不能按原样工作,因为 OP 从一开始就有一个类型错误的变量。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-10-21
        • 2011-08-11
        • 2021-04-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多