简体   繁体   中英

Passing type in Scala as an argument

I want to pass a type to a function in Scala.

Problem in detail

First iteration

I have the following Java classes (coming from an external source):

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 ; in the real problem there are several possibilities.

I want to deserialize a JSON string using a Scala code as follows:

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 .

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. There were compiler and runtime errors as well. Do you know how to pass and how to use such a type in Scala? Thank you!

When you use jackson to parse json, you can use TypeReference to parse generic type. 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. 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:

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

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

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

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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