【问题标题】:Passing type in Scala as an argument将 Scala 中的类型作为参数传递
【发布时间】:2016-08-07 10:53:03
【问题描述】:

我想将类型传递给 Scala 中的函数。

问题详解

第一次迭代

我有以下 Java 类(来自外部源):

public class MyComplexType {
    public String name;
    public int number;
}

public class MyGeneric<T> {
    public String myName;
    public T myValue;
}

在这个例子中,我希望MyComplexTypeMyGeneric 的实际类型;在真正的问题中有几种可能性。

我想使用 Scala 代码反序列化 JSON 字符串,如下所示:

import org.codehaus.jackson.map.ObjectMapper

object GenericExample {
  def main(args: Array[String]) {
    val jsonString = "{\"myName\":\"myNumber\",\"myValue\":{\"name\":\"fifteen\",\"number\":\"15\"}}"
    val objectMapper = new ObjectMapper()
    val myGeneric: MyGeneric[MyComplexType] = objectMapper.readValue(jsonString, classOf[MyGeneric[MyComplexType]])
    val myComplexType: MyComplexType = myGeneric.myValue
  }
}

编译正常但出现运行时错误:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to MyComplexType
        at GenericExample$.main(GenericExample.scala:9)

第二次迭代

问题的有效解决方案:

val jsonString = "{\"myName\":\"myNumber\",\"myValue\":{\"name\":\"fifteen\",\"number\":\"15\"}}"
val objectMapper = new ObjectMapper()
val myGeneric: MyGeneric[MyComplexType] = objectMapper.readValue(jsonString, classOf[MyGeneric[MyComplexType]]) 
myGeneric.myValue = objectMapper.readValue(objectMapper.readTree(jsonString).get("myValue").toString, classOf[MyComplexType])
val myComplexType: MyComplexType = myGeneric.myValue

不好但有效。 (如果有人知道如何让它变得更好,那也欢迎。)

第三次迭代

第二次迭代解决方案中的行在实际问题中多次出现,因此我想创建一个函数。更改变量是 JSON 格式的字符串和 MyComplexType

我想要这样的东西:

def main(args: Array[String]) {
  val jsonString = "{\"myName\":\"myNumber\",\"myValue\":{\"name\":\"fifteen\",\"number\":\"15\"}}"
  val myGeneric = extractMyGeneric[MyComplexType](jsonString)
  val myComplexType: MyComplexType = myGeneric.myValue
}

private def extractMyGeneric[T](jsonString: String) = {
  val objectMapper = new ObjectMapper()
  val myGeneric = objectMapper.readValue(jsonString, classOf[MyGeneric[T]])    
  myGeneric.myValue = objectMapper.readValue(objectMapper.readTree(jsonString).get("myValue").toString, classOf[T])
  myGeneric
}

这不起作用(编译器错误)。我已经玩过ClassClassTagclassOf 的各种组合,但它们都没有帮助。还有编译器和运行时错误。你知道如何在 Scala 中传递和使用这样的类型吗?谢谢!

【问题讨论】:

    标签: java json scala generics reflection


    【解决方案1】:

    使用jackson解析json时,可以使用TypeReference解析generic类型。示例:

    val jsonString = "{\"myName\":\"myNumber\",\"myValue\":{\"name\":\"fifteen\",\"number\":\"15\"}}"
    val objectMapper = new ObjectMapper()
    val reference = new TypeReference[MyGeneric[MyComplexType]]() {}
    val value: MyGeneric[MyComplexType] = objectMapper.readValue(jsonString, reference) 
    

    如果你还想使用Jackson,我想你可以创建一个TypeReference类型的参数。喜欢:

      implicit val typeReference = new TypeReference[MyGeneric[MyComplexType]] {}
      val value = foo(jsonString)
      println(value.myValue.name)
    
    
      def foo[T](jsonStr: String)(implicit typeReference: TypeReference[MyGeneric[T]]): MyGeneric[T] = {
        val objectMapper = new ObjectMapper()
        objectMapper.readValue(jsonStr, typeReference)
      }
    

    【讨论】:

    • 感谢您的回答!扩展它:TypeReference应按如下方式导入:import org.codehaus.jackson.`type`.TypeReference
    • 这是解决“第一次迭代”问题的好方法,谢谢!但是,它并没有解决“第三次迭代”中提出的问题,即主要问题。那仍然是开放的;确实还有其他一些事情要做,因此创建一个单独的函数会很好。最直接的解决方案(将MyGeneric[MyComplexType] 作为泛型传递)会导致运行时错误java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to MyGeneric
    • @CsabaFaragó 你可以查看我的更新,希望对你有帮助。
    • “如果你还想使用Jackson”——确实,我还能用什么?
    • 不错的清单,谢谢! Jackson 似乎是 Scala 中最常用的方法。哪一种是目前最受欢迎的主流解决方案?
    【解决方案2】:

    使用您的方法,我认为这就是您可以使用ClassTags 获得所需课程的方法:

    def extractMyGeneric[A : ClassTag](jsonString: String)(implicit generic: ClassTag[MyGeneric[A]]): MyGeneric[A] = {
      val classOfA = implicitly[ClassTag[A]].runtimeClass.asInstanceOf[Class[A]]
      val classOfMyGenericOfA = generic.runtimeClass.asInstanceOf[Class[MyGeneric[A]]]
      val objectMapper = new ObjectMapper()
      val myGeneric = objectMapper.readValue(jsonString, classOfMyGenericOfA)
      myGeneric.myValue = objectMapper.readValue(objectMapper.readTree(jsonString).get("myValue").toString, classOfA)
      myGeneric
    }
    

    我对 jackson 不熟悉,但在 play-json 中,您可以像这样轻松地为您的泛型类定义 Reads

    import play.api.libs.functional.syntax._
    import play.api.libs.json._
    
    implicit def genReads[A: Reads]: Reads[MyGeneric[A]] = (
      (__ \ "myName").read[String] and
      (__ \ "myValue").read[A]
    )((name, value) => {
        val e = new MyGeneric[A]
        e.myName = name
        e.myValue = value
        e
    })
    

    有了这个,并且假设存在MyComplexTypeReads 实例,您可以将您的方法实现为

    def extractMyGeneric[A: Reads](jsonString: String): MyGeneric[A] = {
      Json.parse(jsonString).as[MyGeneric[A]]
    }
    

    这里的问题是您需要为所有复杂类型提供Reads,这就像

    一样简单
    implicit complexReads: Reads[MyComplexType] = Json.reads[MyComplexType]
    

    如果这些是案例类,否则我认为您必须以类似于我对 genReads 所做的方式手动定义它们。

    【讨论】:

    • 感谢您的详细解答!同时,另一个线程解决了我的问题,以及杰克逊的改进。我将您的解决方案保存为可能的进一步类型相关问题。
    猜你喜欢
    • 2015-03-22
    • 2023-04-01
    • 1970-01-01
    • 2011-06-19
    • 2016-09-12
    • 1970-01-01
    • 2019-02-27
    • 1970-01-01
    • 2015-03-22
    相关资源
    最近更新 更多