Я пытаюсь работать с REST API, который возвращает документ JSON, структура которого зависит от значения свойства с именем type
.
Я определил основной класс следующим образом:
@Serializable class Interaction(
val type: Byte,
val data: InteractionData? = null
)
Структура InteractionData
зависит от значения type
. В настоящее время это интерфейс, от которого наследуют четыре возможные структуры.
Если type
равно 2
, data
должен быть классом с именем ApplicationCommandData
:
@Serializable class ApplicationCommandData(
val id: String,
val name: String
): InteractionData
Если type
равно 3
, data
должен быть классом с именем MessageComponentData
:
@Serializable class MessageComponentData(
val custom_id: String
): InteractionData
Как я могу сделать так, чтобы свойство data
сериализовалось как правильный класс на основе значения свойства type
?
Я попытался установить для свойства data
значение @Transient
, проверить значение type
и создать новую переменную с @SerialName
, установленным для данных внутри блока класса init
, но @SerialData
недействителен для локальных переменных.
Поскольку вы работаете с JSON, вы можете попробовать использовать полиморфную десериализацию на основе контента
@aSemy Попробую это и сообщу, как у меня дела, спасибо
tl;dr: перейти к полному примеру внизу
У вас есть полиморфный класс, и тип определяется свойством вне класса.
{
"type": 2, <- extract this
"data": { <- determined by 'type'
"id": "0001",
"name": "MEGATRON"
}
}
Kotlinx Serialization предоставляет инструменты для этого, но им нужна сборка.
Поскольку вы работаете с JSON, это возможно с помощью полиморфной десериализации на основе контента.
Вот первоначальная реализация, но есть недостаток...
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")
}
}
}
Невозможно выбрать конкретный сериализатор, потому что у Interaction
нет параметра типа, поэтому давайте его добавим.
@Serializable
data class Interaction<T : InteractionData?>( // add a type parameter
val type: Byte,
val data: T? = null
)
Теперь плагин Kotlinx Serialization будет генерировать сериализатор, который принимает сериализатор для T: InteractionData
. Мы можем обновить InteractionJsonSerializer
, чтобы использовать это.
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")
}
}
}
Вот полный работающий пример со всеми импортами.
Я сделал пару изменений в вашем коде.
InteractionData
запечатанный интерфейс, потому что это показалось уместным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")
}
}
}
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))
Просмотрите документы по «закрытому полиморфизму». Обновлено: я внимательно рассмотрел, и я не думаю, что есть встроенное решение для того, что вам нужно.