kotlinx.serialization: Inconsistent behaviour with sealed classes serialization
I faced a problem with sealed classes serialization.
To Reproduce
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
sealed class Project {
abstract val name: String
@Serializable
class OwnedProject(override val name: String, val owner: String) : Project()
}
fun main() {
println(Json.encodeToString(Project.OwnedProject("kotlinx.coroutines", "kotlin")))
// prints: {"name":"kotlinx.coroutines","owner":"kotlin"}
val project: Project = Project.OwnedProject("kotlinx.coroutines", "kotlin")
// prints: {"type":"com.example.Project.OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"}
println(Json.encodeToString(project))
}
I thought that result in both cases should be equal, but it prints different JSON strings.
Environment
- Kotlin version: 1.4.10
- Library version : Kotlinx Serialization Plugin 1.4.10
- Kotlin platforms: JVM
- Gradle version: 6.6.1
- Other relevant context: JRE version: 1.8
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 7
- Comments: 19 (10 by maintainers)
@pdvrieze just because we know the concrete type on serializer side at this time doesn’t mean the deserializer will know what to do, right?
The workarounds mentioned here are not possible for me as I don’t control the place where Kotlinx.Serialization is called. I’m using Spring support for kotlinx serialization, and internally Spring uses
serializer<T>()to find the serializer for the object I give to it.The interface I have with
SimpMessagingTemplateis a generic one (independent from the message converters used) taking just the destination topic, and the payload:The problem is that this means it always has the concrete type, so it never finds it “necessary” to include the type information.
But that is when Spring sends messages over the STOMP websocket. Now when I receive them in the topic I use, I expect any subclass of my sealed class, so the deserializer doesn’t know what to use and fails. I think it would be necessary to have a parameter to include the discriminator all the time.
One workaround that would work is actually wrapping the sealed class into a concrete type with a single field, because the type of the field would be the sealed class parent and solve this problem. But that’s pretty inconvenient to use. It would be much nicer to have a global switch to enable consistent serialization of polymorphic classes.
Is there any way to enable a discriminator field to all class hierarchy forcefully? I mean like
JsonTypeInfoannotation for Jackson mapper or something? If I remember correctly Jackson includes a discriminator field in both cases.My case is related to response serialization in “ktor” route. Because of this problem, it looks like this:
I have to do manual encoding through
encodeToStringand have to specify the “needed” response type explicitly.