Scala - 从String动态实例化类

时间:2014-11-03 17:14:08

标签: json scala reflection

我正在尝试创建一个动态解析器​​,它允许我根据类名将json内容解析为不同的类。

我将得到json和类名(作为String),我想做这样的事情:

val theCaseClassName = "com.ardlema.JDBCDataProviderProperties" 
val myCaseClass = Class.forName(theCaseClassName)
val jsonJdbcProperties = """{"url":"myUrl","userName":"theUser","password":"thePassword"}"""
val json = Json.parse(jsonJdbcProperties)
val value = Try(json.as[myClass])

上面的代码显然无法编译,因为json.as []方法试图将节点转换为" T" (我有一个为我的案例类定义的隐式Reads [T])

什么是获得正确的" T"从原始String传入json.as []方法?

2 个答案:

答案 0 :(得分:2)

可能有效的一个很好的解决方案是进行多态反序列化。这允许你向你的json添加一个字段(比如" type")并允许Jackson(假设你正在使用像杰克逊这样令人敬畏的json解析器)来确定正确的类型代表你。看起来你可能没有使用杰克逊;我保证值得使用。

这个post对多态类型进行了很好的介绍。它涵盖了许多有用的案例,包括您无法修改第三方代码的情况(此处您添加了一个Mixin来注释类型层次结构)。

最简单的情况最终看起来像这样(并且所有这些对Scala对象都有效 - jackson even has a great scala module):

object Test {
  @JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type"
  )
  @JsonSubTypes(Array(
    new Type(value = classOf[Cat], name = "cat"),
    new Type(value = classOf[Dog], name = "dog")
  ))
  trait Animal

  case class Dog(name: String, breed: String, leash_color: String) extends Animal
  case class Cat(name: String, favorite_toy: String) extends Animal

  def main(args: Array[String]): Unit = {
    val objectMapper = new ObjectMapper with ScalaObjectMapper
    objectMapper.registerModule(DefaultScalaModule)

    val dogStr = """{"type": "dog", "name": "Spike", "breed": "mutt",  "leash_color": "red"}"""
    val catStr = """{"type": "cat", "name": "Fluffy", "favorite_toy": "spider ring"}"""

    val animal1 = objectMapper.readValue[Animal](dogStr)
    val animal2 = objectMapper.readValue[Animal](catStr)

    println(animal1)
    println(animal2)
  }
}

这会生成此输出:

// Dog(Spike,mutt,red)
// Cat(Fluffy,spider ring)

您也可以避免列出子类型映射,但它需要json" type"字段有点复杂。试验它;你可能会喜欢它。像这样定义动物:

@JsonTypeInfo(
  use = JsonTypeInfo.Id.CLASS,
  include = JsonTypeInfo.As.PROPERTY,
  property = "type"
)
trait Animal

它产生(并消耗)json像这样:

/*
{
    "breed": "mutt",
    "leash_color": "red",
    "name": "Spike",
    "type": "classpath.to.Test$Dog"
}
{
    "favorite_toy": "spider ring",
    "name": "Fluffy",
    "type": "classpath.to.Test$Cat"
}
*/

答案 1 :(得分:2)

您应该根据班级名称选择Reads[T]。不幸的是,这可能必须是手动模式匹配:

val r: Reads[_] = theCaseClassName match {
  case "com.ardlema.JDBCDataProviderProperties" => JDBCReads
  case ... => ...
}
val value = json.as(r).asInstanceOf[...]

或者,看看json.as的实施情况;在某些时候,它可能需要classTag,然后在其上调用.runtimeClass。假设是这样,你可以做任何事情并在那里传递你自己的myCaseClass