处理类型为Any的混合值而不使用asInstanceOf样板

时间:2017-09-26 10:21:25

标签: scala

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

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

从这些嵌套的Map中提取值似乎需要进行类型测试和转换,这相当丑陋。特别是,我们最终得到了多个与返回类型相同的函数(例如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指出的那样(在我现在删除的答案中),类型擦除会阻止它在运行时检测类型是否正确。

2 个答案:

答案 0 :(得分: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")

: T可用时,特别针对ClassTag[T]进行模式匹配。

不幸的是,如果您希望T本身是通用类型,请键入删除回击:Getter[List[String]]只能检查它是否通过List,而不是其类型参数。

答案 1 :(得分:0)

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

相关问题