I have the following generic sealed class representing the status of network response.
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "status")
@JsonSubTypes(
value = [
JsonSubTypes.Type(value = Response.OK::class, name = "OK"),
JsonSubTypes.Type(value = Response.Error::class, name = "ERROR"),
]
)
sealed class Response<out Content, out Error> {
data class OK<out Content>(val content: Content) : Response<Content, Nothing>()
data class Error<out Error>(val error: Error) : Response<Nothing, Error>()
}
The network response json can have status": "OK"
in which case it contains a content
key or a "status": "ERROR"
in which case it contains a error
key.
The type under the content
and error
key can be different for each endpoint I’m talking to. Hence the need for generic types
So for example one endpoit returns String
as types, so Response<String, String>
{
"status": "OK",
"content": "Hello"
}
{
"status": "ERROR",
"error": "MyError"
}
Another endpoit return Double
as content and Int
as error, so
Response<Double, Int>
{
"status": "OK",
"content": 2.0
}
{
"status": "ERROR",
"error": 1
}
My parsing fails though with message
Could not resolve type id 'OK' as a subtype of `com.example.models.Response<java.lang.String,java.lang.String>`: Failed to specialize base type com.example.models.Response<java.lang.String,java.lang.String> as com.example.models.Response$OK, problem: Type parameter #1/2 differs; can not specialize java.lang.String with java.lang.Object
at [Source: (String)"{
"status": "OK",
"content": "Hello"
}"; line: 2, column: 13]
@Nested
inner class ContentParsingTests {
@Test
fun `parses OK response`() {
val json = """
{
"status": "OK",
"content": "Hello"
}
""".trimIndent()
when (val result = objectMapper.readValue<Response<String, String>>(json)) {
is Response.OK -> {
assertEquals(result.content, "Hello")
}
is Response.Error -> {
fail()
}
}
}
@Test
fun `parses ERROR response`() {
val json = """
{
"status": "ERROR",
"error": "MyError"
}
""".trimIndent()
when (val result = objectMapper.readValue<Response<String, String>>(json)) {
is Response.OK -> {
fail()
}
is Response.Error -> {
assertEquals(result.error, "MyError")
}
}
}
}
I noticed that the parsing works fine if only the content is generic:
sealed class Response<out Content > {
data class OK<out Content>(val content: Content) : Response<Content>()
object Error : Response<Nothing>()
}
but of course I loose the error payload
What would be a correct way to parse the json into my generic class?
2
Answers
You don’t need generics at all for this case. Just have:
Jackson will then be able to parse everything correctly.
I think the issue is with
Nothing
because it’s likeVoid
and you can’t create an instance of it or get a type information that’s why the serialization library struggling with it. so a solution for the current problem is to update the model definition like this and it works. It’s not ideal though.