1

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?

Water Cooler v2
  • 32,724
  • 54
  • 166
  • 336
  • 3
    You should be using the Jackson-Kotiln module and then none of this is likely needed. https://github.com/FasterXML/jackson-module-kotlin – Jayson Minard Sep 07 '16 at 22:17
  • @JaysonMinard Thank you. I have downloaded it and built a jar out of it using Maven. I will be using it going forward. – Water Cooler v2 Sep 07 '16 at 22:21
  • 2
    It has artifacts in Maven Central, no need to build it... https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-kotlin/2.8.2 – Jayson Minard Sep 07 '16 at 22:22
  • Thank you. I guess I am going to have to read a lot of documentation before I can make any use of Maven or Gradle properly. I intend to. – Water Cooler v2 Sep 07 '16 at 22:26

3 Answers3

4

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)
rafal
  • 3,120
  • 1
  • 17
  • 12
  • 1
    Thank you. I'd actually tried that as well but the intellisense fails to propose any suggestions once I type `OperationResult::class.`. I believe it is an issue with the Kotlin plug-in for Eclipse in that it fails to load the kotlin reflection assembly. But I can't be sure. I am switching IDE's to IntelliJ IDEA Ultimate and will report back if the solution you suggest works, which I am positive it will. Thanks again. :-) – Water Cooler v2 Sep 07 '16 at 19:36
  • Even the free version (Intellij IDEA Community) works great with Kotlin :) – rafal Sep 07 '16 at 19:41
  • You are right, it does but I want to write servlets, which the Community Edition does not support. http://stackoverflow.com/q/39072303/303685 – Water Cooler v2 Sep 07 '16 at 19:43
  • You can write servlets @WaterCoolerv2 but you may have to startup the application server. You can always use embedded Jetty, Undertow or Vert.x which you just run as apps and your code will be compiled with them. And your code can be servlets. – Jayson Minard Sep 07 '16 at 22:16
  • @JaysonMinard I am in an unexplored turf when it comes to Java or Kotlin. All of those tools are new to me. I am scared. :-) I am just practicing. I will try them out in time. For me just now, the largest obstacle seems to be the plethora of tools you have to use and their documentation just to do simple stuff. With .NET, it's all under one roof and really very simple. – Water Cooler v2 Sep 07 '16 at 22:23
4

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."

Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
  • Thank you for such a lovely and detailed answer. I loved all the explanation. I am aware of and have used object declarations, object expressions (to create substitutes of Java's lambda expressions, for .e.g. inline mouselisteners, etc.) and companion objects. I understand what's happening when you say `object : TypeReference() { }`. Still, I really liked the way you explain things. Thank you very much for your time. :-) – Water Cooler v2 Sep 08 '16 at 09:47
3

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)
miensol
  • 39,733
  • 7
  • 116
  • 112
  • Many thanks. The Kotlin plug-in for Eclipse, which I am using does not resolve the type `TypeReference`. It also offers little to no intellisense in finding where a type you want to reference belongs. Could you please tell me the package I need to import to get it? – Water Cooler v2 Sep 07 '16 at 19:40