Декодирование 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
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
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

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