jackson-module-kotlin icon indicating copy to clipboard operation
jackson-module-kotlin copied to clipboard

Kotlin Serialization of Polymorphic Interfaces Erases Parent Type

Open MrThreepwood opened this issue 3 years ago • 0 comments

When working with a fairly complex api that we have, we need to be able to except a list of items, which are defined as a specific type based on a "type" property on the model, and further distinguished by a "version" property. For example, we may receive a Ticket, or a User in this payload, and then if it is a Ticket, it may be a V1 or V2 ticket. This was working up until I attempted to label our top level interface as a sealed interface. Suddenly the type field was being filled with the version value on serialization. I attempted to come up with a minimal reproduction test, but I am not actually able to come up with a version that works (though without the sealed label it is now working in our code:

import com.fasterxml.jackson.annotation.JsonSubTypes import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import org.junit.jupiter.api.Test

class TestStuff {
    @Test
    fun `test`() {
        val writer = jacksonObjectMapper().writer().forType(jacksonTypeRef<List<AbstractData>>())
        println(writer.writeValueAsString(listOf(StractV1("hello"), StractV2(15))))
    }
}


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(value = Stract::class, name = "Stract")
)
interface AbstractData {
    fun stuff(): String = "12"
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "version")
@JsonSubTypes(
    JsonSubTypes.Type(value = StractV1::class, name = "1.0.0"),
    JsonSubTypes.Type(value = StractV2::class, name = "1.1.0")
)
interface Stract : AbstractData

class StractV1(val id: String) : Stract

class StractV2(val number: Int) : Stract

The output of this code is: [{"type":"1.0.0","id":"hello"},{"type":"1.1.0","number":15}], but should be: [{"type":"Stract","version":"1.0.0","id":"hello"},{"type":"Stract", "version":"1.1.0","number":15}]. I was expecting that this would work with an interface, but would fail with a sealed interface, however, I have not been able to get it to work at all.

Versions Kotlin: 1.5.21 Jackson BOM: 2.12.1

MrThreepwood avatar Jan 14 '22 18:01 MrThreepwood