Skip to content

Polymorphic sealed class missing type information #765

Open
@MaxDaten

Description

@MaxDaten

Your question

Hi,

maybe I'm missing some important piece or my thinking is a bit too naïve, but I try to write a generic message passing library based on a json protocol.

package com.fasterxml.jackson.module.kotlin.test.github

import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import junit.framework.TestCase.assertEquals
import org.junit.Assert.assertThrows
import kotlin.test.Test

class ReproductionTest {

    @JsonTypeInfo(use = JsonTypeInfo.Id.SIMPLE_NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type")
    sealed class Response<out Err, out B>{
        data class Success<out B>(val response: B) : Response<Nothing, B>()
        data class Failure<out Err>(val error: Err) : Response<Err, Nothing>()
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.SIMPLE_NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type")
    sealed class Message {
        data class Text(val text: String) : Message()
        data class Image(val url: String) : Message()
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.SIMPLE_NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type")
    sealed class Error {
        data class NotFound(val message: String) : Error()
        data class BadRequest(val message: String) : Error()
    }

    val asJson = """{"@type":"Success","response":{"@type":"Text","text":"foo"}}"""
    val responseSuccess: Response<Error, Message> = Response.Success(Message.Text("foo"))

    @Test
    fun testReadAsValue() {
        val mapper = jacksonObjectMapper()
        val refType = mapper.typeFactory.constructParametricType(Response::class.java, Error::class.java, Message::class.java)

        // Failing com.fasterxml.jackson.databind.exc.InvalidTypeIdException:
        // Could not resolve type id 'Success' as a subtype of ...
        assertEquals(
            responseSuccess,
            mapper.readValue<Response<Error, Message>>(asJson, refType)
        )
    }

    @Test
    fun testWriteAsValue() {
        val mapper = jacksonObjectMapper()

        // Failing
        assertEquals(
            asJson,
            mapper.writeValueAsString(responseSuccess) // actual: {"@type":"Success","response":{"text":"foo"}}
        )
    }
}

My expectations/goals would be:

  1. if defined at the root type of Err/B type information should be included in the "response": {...} value and should be in control of the consuming code side (Message & Error would be defined there). But the type information is missing the the serialized value
  2. and of course it should be deserializable

Is that possible to achieve? I tried various variations, including mapper.writerFor(refType), without any success.

Help is appreciated!

Greetings

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions