简体   繁体   English

将Scala中的类型作为参数传递

[英]Passing type in Scala as an argument

I want to pass a type to a function in Scala. 我想将类型传递给Scala中的函数。

Problem in detail 问题详细

First iteration 第一次迭代

I have the following Java classes (coming from an external source): 我有以下Java类(来自外部源):

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

and

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

In this example I want MyComplexType to be the the actual type of MyGeneric ; 在这个例子中,我想MyComplexType是实际类型的MyGeneric ; in the real problem there are several possibilities. 在真正的问题中有几种可能性。

I want to deserialize a JSON string using a Scala code as follows: 我想使用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
  }
}

it compiles fine but runtime error occurs: 它编译好,但发生运行时错误:

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

Second iteration 第二次迭代

Working solution to the problem: 解决问题的工作方案:

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

Not nice but works. 不好但是很有效。 (If anybody knows how to make it better, that would also welcome.) (如果有人知道如何让它变得更好,那也会受到欢迎。)

Third iteration 第三次迭代

The lines in the solution of second iteration occur in the real problem several times, therefore I want to create a function. 第二次迭代解决方案中的行多次出现在实际问题中,因此我想创建一个函数。 The altering variables are the JSON formatted string and the MyComplexType . 更改变量是JSON格式的字符串和MyComplexType

I want something like this: 我想要这样的东西:

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
}

This does not work (compiler error). 这不起作用(编译器错误)。 I've already played around with various combinations of Class , ClassTag , classOf but none of them helped. 我已经玩过ClassClassTagclassOf各种组合,但它们都没有帮助。 There were compiler and runtime errors as well. 还有编译器和运行时错误。 Do you know how to pass and how to use such a type in Scala? 你知道如何传递以及如何在Scala中使用这种类型吗? Thank you! 谢谢!

When you use jackson to parse json, you can use TypeReference to parse generic type. 当您使用jackson解析json时,您可以使用TypeReference来解析generic类型。 Example: 例:

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) 

if you still want to use Jackson , I think you can create a parameter with TypeReference type. 如果您仍想使用Jackson ,我认为您可以使用TypeReference类型创建参数。 like: 喜欢:

  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)
  }

Using your approach, I think this is how you can get classes that you need using ClassTag s: 使用您的方法,我认为您可以使用ClassTag获取所需的类:

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
}

I am not familiar with jackson but in play-json you could easily define Reads for your generic class like this 我对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
})

Having this, and provided that instance of Reads for MyComplexType exists, you can implement your method as 有了这个,并且只要存在MyComplexTypeReads实例,就可以实现你的方法

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

the issue here is that you need to provide Reads for all of your complex types, which would be as easy as 这里的问题是你需要为所有复杂类型提供Reads ,这很简单

implicit complexReads: Reads[MyComplexType] = Json.reads[MyComplexType]

if those were case classes, otherways I think you would have to define them manually in simillar way to what I've done with genReads . 如果那些是案例类,那么我认为你必须用genReads方式手动定义它们与genReads所做的genReads

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM