Декодирование JSON в класс на основе значения другого ключа JSON

Я пытаюсь работать с 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 недействителен для локальных переменных.

Просмотрите документы по «закрытому полиморфизму». Обновлено: я внимательно рассмотрел, и я не думаю, что есть встроенное решение для того, что вам нужно.

aSemy 18.11.2022 13:06

Поскольку вы работаете с JSON, вы можете попробовать использовать полиморфную десериализацию на основе контента

aSemy 18.11.2022 13:15

@aSemy Попробую это и сообщу, как у меня дела, спасибо

Adam Chance 18.11.2022 13:25
Шаблоны Angular PrimeNg
Шаблоны Angular PrimeNg
Как привнести проверку типов в наши шаблоны Angular, использующие компоненты библиотеки PrimeNg, и настроить их отображение с помощью встроенной...
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Если вы веб-разработчик (или хотите им стать), то вы наверняка гик и вам нравятся "Звездные войны". А как бы вы хотели, чтобы фоном для вашего...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Начала с розового дизайна
Начала с розового дизайна
Pink Design - это система дизайна Appwrite с открытым исходным кодом для создания последовательных и многократно используемых пользовательских...
Шлюз в PHP
Шлюз в PHP
API-шлюз (AG) - это сервер, который действует как единая точка входа для набора микросервисов.
14 Задание: Типы данных и структуры данных Python для DevOps
14 Задание: Типы данных и структуры данных Python для DevOps
проверить тип данных используемой переменной, мы можем просто написать: your_variable=100
2
3
96
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

tl;dr: перейти к полному примеру внизу

Краткое описание проблемы

У вас есть полиморфный класс, и тип определяется свойством вне класса.

{
  "type": 2,  <- extract this
  "data": {   <- determined by 'type'
    "id": "0001",
    "name": "MEGATRON"
  }
}

Kotlinx Serialization предоставляет инструменты для этого, но им нужна сборка.

Полиморфная десериализация на основе содержимого JSON

Поскольку вы работаете с 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")
    }
  }
}

Полный пример

Вот полный работающий пример со всеми импортами.

Я сделал пару изменений в вашем коде.

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))

Версии

  • Котлин 1.7.21
  • Сериализация Котлинкс 1.4.1

Другие вопросы по теме