'How to use Jackson JsonSubTypes annotation in Kotlin

I'm trying to convert some Java code that uses Jackson's @JsonSubTypes annotation to manage polymorphism.

Here is the working Java code:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = Comment.class, name = "CommentNote"),
    @JsonSubTypes.Type(value = Photo.class, name = "PhotoNote"),
    @JsonSubTypes.Type(value = Document.class, name = "DocumentNote")
})
public abstract class Note implements Identifiable {
    [...]

Here is the Kotlin code I think would be equivalent:

JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type")
JsonSubTypes(
    JsonSubTypes.Type(value = javaClass<Comment>(), name = "CommentNote"),
    JsonSubTypes.Type(value = javaClass<Photo>(), name = "PhotoNote"),
    JsonSubTypes.Type(value = javaClass<Document>(), name = "DocumentNote")
)
abstract class Note : Identifiable {
    [...]

But I get the following errors on each of the three "JsonSubTypes.Type" lines :

Kotlin: An annotation parameter must be a compile-time constant
Kotlin: Annotation class cannot be instantiated

Any idea?



Solution 1:[1]

Turns out it's a bug in the compiler, thanks for reporting it. To work around this issue, you can import JsonSubTypes.Type and use it without qualification:

import org.codehaus.jackson.annotate.JsonSubTypes.Type

JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type")
JsonSubTypes(
    Type(value = javaClass<Comment>(), name = "CommentNote"),
    Type(value = javaClass<Photo>(), name = "PhotoNote"),
    Type(value = javaClass<Document>(), name = "DocumentNote")
)
abstract class Note : Identifiable {
    [...]

Solution 2:[2]

I believe this has been resolved and nowadays you can write it like this:

import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo

@JsonTypeInfo(
   use = JsonTypeInfo.Id.NAME,
   include = JsonTypeInfo.As.PROPERTY,
   property = "type")
   @JsonSubTypes(
       JsonSubTypes.Type(value = Comment::class, name = "CommentNote"),
       JsonSubTypes.Type(value = Photo::class, name = "PhotoNote"),
       JsonSubTypes.Type(value = Document::class, name = "DocumentNote"))
interface Note

Note the missing @ and class notation in the JsonSubTypes.Type.

Solution 3:[3]

I know this is an old question, nevertheless if someone is still searching for a solution to serialise/deserialise inherited classes in kotlin with jackson, I'd suggest using sealed classes and not using @JsonSubTypes.

I'd also suggest using include as EXISTING_PROPERTY and getting the property through a val inside the sealed class. Otherwise, if you add combined inherited objects inside an array, jackson won't be able to deserialise and will throw a com.fasterxml.jackson.databind.exc.InvalidTypeIdException.

Here is the example usage:

@JsonTypeInfo(
   use = JsonTypeInfo.Id.NAME,
   include = JsonTypeInfo.As.EXISTING_PROPERTY,
   property = "type")
sealed class Note{
    val type = this::class.java.simpleName
}
data class Document(val text: String, ...) : Note()

this should work like a charm, including using this class inside an array!

Another big advantage of this approach is, you don't need to set anything manually. As we all know, manual operations are error prone, as you can forget to add/remove/modify in case you add or remove a sub class, modify name etc. In this approach, you neither need to have a manually carved sub type list, nor need to give a json representation of the class name manually.

Solution 4:[4]

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(Car::class, name = "car"),
    JsonSubTypes.Type(Truck::class, name = "truck")
)
abstract class Vehicle(val type: String)
data class Car @JsonCreator constructor(@JsonProperty("manufacturer") val manufacturer: String) : Vehicle("car")
data class Truck @JsonCreator constructor(@JsonProperty("weight") val weight: Double) : Vehicle("truck")

@Test
public fun jacksonInheritanceKt() {
    val s = "[{\"type\": \"car\", \"manufacturer\": \"audi\"}, {\"type\": \"truck\", \"weight\": 3000.0}]"

    val mapper = ObjectMapper()
    val vehicles = mapper.readValue(s, object : TypeReference<List<Vehicle>>() {})
    println(vehicles)
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Andrey Breslav
Solution 2 naXa stands with Ukraine
Solution 3
Solution 4 Vladimir Kornyshev