tl;dr: skip to the full example at the bottom
Problem summary
You have a polymorphic class, and the type is determined by a property outside of the class.
{
"type": 2, <- extract this
"data": { <- determined by 'type'
"id": "0001",
"name": "MEGATRON"
}
}
Kotlinx Serialization provides the tools to handle this - but they need some assembly.
JSON content based polymorphic deserialization
Since you're working with JSON this is possible using content based polymorphic deserialization.
Here's an initial implementation, but there's a flaw...
object InteractionJsonSerializer : JsonContentPolymorphicSerializer<Interaction>(
Interaction::class
) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out Interaction> {
// extract the type from the plain JSON object
val type = element.jsonObject["type"]?.jsonPrimitive?.intOrNull
println("found InteractionData type: $type")
return when (type) {
// can't specify the type of InteractionData
2 -> Interaction.serializer()
3 -> Interaction.serializer()
else -> error("unknown type $type")
}
}
}
It's not possible to select a specific serializer, because Interaction
doesn't have a type parameter, so let's add one.
@Serializable
data class Interaction<T : InteractionData?>( // add a type parameter
val type: Byte,
val data: T? = null
)
Now the Kotlinx Serialization plugin will generate a serializer that accepts a serializer for T: InteractionData
. We can update InteractionJsonSerializer
to make use of this.
object InteractionJsonSerializer : JsonContentPolymorphicSerializer<Interaction<*>>(
Interaction::class
) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out Interaction<*>> {
val type = element.jsonObject["type"]?.jsonPrimitive?.intOrNull
println("found InteractionData type: $type")
return when (type) {
// now the type can be specified
2 -> Interaction.serializer(ApplicationCommandData.serializer())
3 -> Interaction.serializer(MessageComponentData.serializer())
else -> error("unknown type $type")
}
}
}
Complete example
Here's a complete, runnable example, with all the imports.
I made a couple of tweaks to your code.
- I made
InteractionData
a sealed interface, because it seemed appropriate
- I converted the classes to data classes, so Kotlin generates a nice
toString()
.
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
fun main() {
val interactionType2 =
Json.decodeFromString(
InteractionJsonSerializer,
/*language=JSON*/
"""
{
"type": 2,
"data": {
"id": "0001",
"name": "MEGATRON"
}
}
""".trimIndent()
)
println(interactionType2)
val interactionType3 =
Json.decodeFromString(
InteractionJsonSerializer,
/*language=JSON*/
"""
{
"type": 3,
"data": {
"custom_id": "abc123"
}
}
""".trimIndent()
)
println(interactionType3)
}
@Serializable
data class Interaction<T : InteractionData?>(
val type: Byte,
val data: T? = null
)
sealed interface InteractionData
@Serializable
data class ApplicationCommandData(
val id: String,
val name: String
) : InteractionData
@Serializable
data class MessageComponentData(
val custom_id: String
) : InteractionData
object InteractionJsonSerializer : JsonContentPolymorphicSerializer<Interaction<*>>(
Interaction::class
) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out Interaction<*>> {
val type = element.jsonObject["type"]?.jsonPrimitive?.intOrNull
println("found InteractionData type: $type")
return when (type) {
2 -> Interaction.serializer(ApplicationCommandData.serializer())
3 -> Interaction.serializer(MessageComponentData.serializer())
else -> error("unknown type $type")
}
}
}
Output
found InteractionData type: 2
Interaction(type=2, data=ApplicationCommandData(id=0001, name=MEGATRON))
found InteractionData type: 3
Interaction(type=3, data=MessageComponentData(custom_id=abc123))
Versions
- Kotlin 1.7.21
- Kotlinx Serialization 1.4.1