Десериализация массива JSON с разными типами значений с помощью библиотеки kotlinx.serialization

#json #kotlin #serialization #kotlinx.serialization

#json #kotlin #сериализация #kotlinx.serialization

Вопрос:

Я пытаюсь десериализовать следующую строку:

  val stringJson = "{"decomposed":[", ",{"id":4944372,"name":"Johny","various":false,"composer":false,"genres":[]}]}"
   
  

Десериализация отлично работает со следующим кодом

 @Serializable
data class Artist(
    val decomposed: JsonArray
)

fun main() {
    val jsonString = "{"decomposed":[", ",{"id":4944372,"name":"Johny","various":false,"composer":false,"genres":[]}]}"
    println(Json.decodeFromString<Artist>(jsonString))
}
  

Но я хочу сделать что-то вроде

 @Serializable
class Decomposed {
    @Serializable
    class DecomposedClassValue(val value: DecomposedClass)

    @Serializable
    class StringValue(val value: String)
}


@Serializable
data class DecomposedClass(
    val id: Long? = null,
    val name: String? = null,
    val various: Boolean? = null,
    val composer: Boolean? = null,
    val genres: JsonArray? = null
)

@Serializable
data class Artist(
    val decomposed: List<Decomposed>
)

fun main() {
    val jsonString = "{"decomposed":[", ",{"id":4944372,"name":"Johny","various":false,"composer":false,"genres":[]}]}"
    println(Json.decodeFromString<Artist>(jsonString))
}
  

Но kotlinx.serialization ожидаемый сбой с JsonDecodingException: Unexpected JSON token at offset 15: Expected '{, kind: CLASS'
И я не могу понять, как я могу переписать свою Decomposed работу по десериализации so. Не могли бы вы мне помочь?

Комментарии:

1. Можно ли настроить формат JSON или это жесткое требование?

Ответ №1:

То, что вы пытаетесь сделать, называется полиморфной десериализацией. Для этого требуется, чтобы целевые классы десериализации имели общий суперкласс (предпочтительно закрытый):

 @Serializable
data class Artist(
    val decomposed: List<Decomposed>
)

@Serializable
sealed class Decomposed

@Serializable
class StringValue(val value: String) : Decomposed() //Can't add superclass to String, so we have to create a wrapper class which we could make extend Decomposed

@Serializable
data class DecomposedClass(
    val id: Long? = null,
    val name: String? = null,
    val various: Boolean? = null,
    val composer: Boolean? = null,
    val genres: JsonArray? = null
) : Decomposed() //DecomposedClassValue is redundant, we may extend DecomposedClass from Decomposed directly
  

Это позволит вам десериализовать JSON следующего формата:

 val jsonString = "{"decomposed":[{"type":"StringValue", "value":","}, {"type":"DecomposedClass", "id":4944372,"name":"Johny","various":false,"composer":false,"genres":[]}]}" 
  

Поскольку в исходном JSON нет дескриптора класса, библиотека сериализации не может определить фактический сериализатор, который следует использовать для десериализации класса Kotlin. Вам нужно будет написать пользовательский JsonContentPolymorphicSerializer и подключить его к Decomposed классу; также вам нужно написать пользовательский сериализатор для StringValue class, поскольку он представлен в JSON как a String , а не как JSONObject с value полем String типа:

 object DecomposedSerializer : JsonContentPolymorphicSerializer<Decomposed>(Decomposed::class) {
    override fun selectDeserializer(element: JsonElement) = when {
        element is JsonPrimitive -> StringValue.serializer()
        else -> DecomposedClass.serializer()
    }
}

object StringValueSerializer : KSerializer<StringValue> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("StringValue")

    override fun deserialize(decoder: Decoder): StringValue {
        require(decoder is JsonDecoder)
        val element = decoder.decodeJsonElement()
        return StringValue(element.jsonPrimitive.content)
    }

    override fun serialize(encoder: Encoder, value: StringValue) {
        encoder.encodeString(value.value)
    }
}

@Serializable(with = DecomposedSerializer::class)
sealed class Decomposed

@Serializable(with = StringValueSerializer::class)
class StringValue(val value: String) : Decomposed()

  

Это позволит вам десериализовать JSON исходного формата.