Water Cooler v2
Water Cooler v2

Reputation: 33850

How do I get the class name from a type name?

I am trying to deserialize a Json string into an object of type OperationResult<String> using Jackson with Kotlin.

I need to construct a type object like so:

val mapper : ObjectMapper = ObjectMapper();
val type : JavaType = mapper.getTypeFactory()
         .constructParametricType(*/ class of OperationResult */,, 
          /* class of String */);
val result : OperationResult<String> = mapper.readValue(
                  responseString, type);

I've tried the following but they do not work.

val type : JavaType = mapper.getTypeFactory()
            .constructParametricType(
             javaClass<OperationResult>, 
             javaClass<String>); // Unresolved javaClass<T>

val type : JavaType = mapper.getTypeFactory()
            .constructParametricType(
             OperationResult::class, 
             String::class);

How do I get a java class from the type names?

Upvotes: 1

Views: 5868

Answers (3)

Jayson Minard
Jayson Minard

Reputation: 85946

Kotlin has a few things that become a concern when using Jackson, GSON or other libraries that instantiate Kotlin objects. One, is how do you get the Class, TypeToken, TypeReference or other specialized class that some libraries want to know about. The other is how can they construct classes that do not always have default constructors, or are immutable.

For Jackson, a module was built specifically to cover these cases. It is mentioned in @miensol's answer. He shows an example similar to:

import com.fasterxml.jackson.module.kotlin.*  // added for clarity

val operationalResult: OperationalResult<Long> = mapper.readValue(""{"result":"5"}""")

This is actually calling an inline extension function added to ObjectMapper by the Kotlin module, and it uses the inferred type of the result grabbing the reified generics (available to inline functions) to do whatever is needed to tell Jackson about the data type. It creates a Jackson TypeReference behind the scenes for you and passes it along to Jackson. This is the source of the function:

inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readValue(content, object: TypeReference<T>() {})  

You can easily code the same, but the module has a larger number of these helpers to do this work for you. In addition it handles being able to call non-default constructors and static factory methods for you as well. And in Jackson 2.8.+ it also can deal more intelligently with nullability and default method parameters (allowing the values to be missing in the JSON and therefore using the default value). Without the module, you will soon find new errors.

As for your use of mapper.typeFactory.constructParametricType you should use TypeReference instead, it is much easier and follows the same pattern as above.

val myTypeRef = object: TypeReference<SomeOtherClass>() {}

This code creates an anonymous instance of a class (via an object expression) that has a super type of TypeRefrence with your generic class specified. Java reflection can then query this information.

Be careful using Class directly because it erases generic type information, so using SomeOtherClass::class or SomeOtherClass::class.java all lose the generics and should be avoided for things that require knowledge of them.

So even if you can get away with some things without using the Jackson-Kotlin module, you'll soon run into a lot of pain later. Instead of having to mangle your Kotlin this module removes these types of errors and lets you do things more in the "Kotlin way."

Upvotes: 4

miensol
miensol

Reputation: 41608

The following works as expected:

val type = mapper.typeFactory.constructParametricType(OperationalResult::class.java, String::class.java)
val operationalResult = mapper.readValue<OperationalResult<String>>("""{"result":"stack"}""", type)
println(operationalResult.result) // -> stack

A simpler alternative to deserialize generic types using com.fasterxml.jackson.core.type.TypeReference:

val operationalResult = mapper.readValue<OperationalResult<Double>>("""{"result":"5.5"}""",
        object : TypeReference<OperationalResult<Double>>() {})
println(operationalResult.result) // -> 5.5

And with the aid of jackson-kotlin-module you can even write:

val operationalResult = mapper.readValue<OperationalResult<Long>>("""{"result":"5"}""")
println(operationalResult.result)

Upvotes: 3

rafal
rafal

Reputation: 3260

You need to obtain instance of Class not KClass. To get it you simply use ::class.java instead of ::class.

val type : JavaType = mapper.typeFactory.constructParametricType(OperationResult::class.java, String::class.java)

Upvotes: 4

Related Questions