【问题标题】:Handling mixed values of type Any without asInstanceOf boilerplate在没有 asInstanceOf 样板的情况下处理 Any 类型的混合值
【发布时间】:2017-09-26 10:21:25
【问题描述】:

任何可以返回多个类但在 Any 类型的集合中的 API 都会出现此问题。

一个具体的例子是使用内置的 JSON 解析器 (scala.util.parsing.json) 处理 JSON:返回的值是 Map[String,Any],因为每个 JSON 键值对中的值可以是任何 JSON 类型。

从这些嵌套的Maps 中提取值似乎需要类型测试和强制转换,这相当难看。特别是,我们最终得到了多个相同的函数,除了用于检查和强制转换的返回类型(例如 String、Double、Map...)。

是否可以抽象出这种类型,从而只需要一个通用的get[T](...): T 函数,避免这种样板?

我一直在查看TypeTag,但到目前为止我发现的所有示例都着眼于抽象参数类型,而不是返回类型。

澄清一下:我知道还有许多其他 JSON 解析器提供更好的模式匹配接口等,但我只是对重构这个一般问题感兴趣,用于处理返回 Any 集合的旧接口。

import scala.util.parsing.json._

object ParseJSON {

  val text = """{"str":"A String", "num":123, "obj": { "inner":"value" }}"""

  val json = JSON.parseFull(text).get.asInstanceOf[Map[String,Any]]
      //> ... Map(str -> A String, num -> 123.0, obj -> Map(inner -> value))

  // Three essentially identical functions:

  def getString(m:Map[String,Any], k:String): Option[String] = {
    m.get(k).flatMap{ v =>
      if (v.isInstanceOf[String]) Some(v.asInstanceOf[String]) else None
    }
  }                                             

  def getDouble(m:Map[String,Any], k:String): Option[Double] = {
    m.get(k).flatMap{ v =>
      if (v.isInstanceOf[Double]) Some(v.asInstanceOf[Double]) else None
    }
  }                                              

  def getObject(m:Map[String,Any], k:String): Option[Map[String, Any]] = {
    m.get(k).flatMap{ v =>
      if (v.isInstanceOf[Map[_,_]]) Some(v.asInstanceOf[Map[String,Any]])
      else None
    }
  }                                               

  getString(json, "str")             //> res0: Option[String] = Some(A String)
  getString(json, "num")             //> res1: Option[String] = None
  getObject(json, "obj") 
                   //> res3: Option[Map[String,Any]] = Some(Map(inner -> value))
}

我最初认为这可以通过泛型类来解决:

  class Getter[T] {
    def get(m: Map[String, Any], k: String): Option[T] = {
      m.get(k).flatMap { v =>
        if (v.isInstanceOf[T]) Some(v.asInstanceOf[T]) else None
      }
    }
  }

  new Getter[String].get(json, "str")

但正如 Oleg Pyzhcov 指出的(在我现在已删除的答案中),类型擦除会阻止它在运行时检测类型是否正确。

【问题讨论】:

  • 不再推荐包scala.util.parsing.json。而是使用 JSON 专用库
  • 同意 - 但正如我在问题中所说,我对如何解决这个重构问题很感兴趣(以 JSON 解析为例)
  • 修复一个不鼓励的库的限制几乎没有价值

标签: scala


【解决方案1】:

尝试失败的解决方法非常简单:

import scala.reflect.ClassTag

class Getter[T: ClassTag] {
  def get(m: Map[String, Any], k: String): Option[T] = {
    m.get(k).flatMap {
      case v: T => Some(v) 
      case _ => None
    }
  }
}

new Getter[String].get(json, "str")

ClassTag[T] 可用时,会专门处理针对: T 的模式匹配。

不幸的是,如果您希望 T 本身成为泛型类型,则类型擦除会反击:Getter[List[String]] 只能检查是否传递了 List,而不是其类型参数。

【讨论】:

    【解决方案2】:

    您可能希望为此目的使用 JSON 库,我个人建议 Circe。它可以流畅地处理类型安全的序列化和反序列化,并且可以轻松扩展。但是,如果您正在寻找多态反序列化,则需要将类型存储在 JSON 中,Circe 通常也会通过支持案例类的密封层次结构来处理这些。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-10
      • 1970-01-01
      • 2022-11-03
      • 1970-01-01
      • 1970-01-01
      • 2011-08-28
      相关资源
      最近更新 更多