Почему сериализация коллекций разных типов элементов не поддерживается в ktor-сериализации?

#kotlin #ktor #kotlinx.serialization

#kotlin #ktor #kotlinx.serialization

Вопрос:

Я пытаюсь создать простой сервер, который выдает сериализованный список в формате JSON. Список, подлежащий сериализации, является примером в разделе полиморфной сериализации официального сообщения в блоге.

Но с функцией сериализации ktor я получаю следующее исключение.

 21:53:25.536 [nioEventLoopGroup-4-1] ERROR ktor.application - Unhandled: GET - /
java.lang.IllegalStateException: Serializing collections of different element types is not yet supported. Selected serializers: [DirectMessage, BroadcastMessage]
    at io.ktor.serialization.SerializerLookupKt.elementSerializer(SerializerLookup.kt:71)
  

Поскольку запечатанный класс является ключевой особенностью при выборе Kotlin, мне действительно интересно, почему это не поддерживается.

Есть ли какие-либо веские причины, по которым ktor-сериализация не поддерживает это? Или я должен опубликовать проблему для удаления этой проверки из SerializerLookup.kt?


Я создал этот код, выбрав Новый проект> Kotlin> Приложение в IntelliJ. Измененный код показан ниже.

Мой server.kt:

 import io.ktor.application.*
import io.ktor.features.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.serialization.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import kotlinx.serialization.Serializable

@Serializable
sealed class Message {
    abstract val content: String
}

@Serializable
data class BroadcastMessage(override val content: String) : Message()

@Serializable
data class DirectMessage(override val content: String, val recipient: String) : Message()

val data: List<Message> = listOf(
    DirectMessage("Hey, Joe!", "Joe"),
    BroadcastMessage("Hey, all!")
)

fun main() {
    embeddedServer(Netty, port = 8080, host = "127.0.0.1") {
        install(ContentNegotiation) {
            json()
        }
        routing {
            get("/") {
                call.respond(data)
            }
        }
    }.start(wait = true)
}
  

Мой build.gradle.kts:

 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.4.10"
    application
    kotlin("plugin.serialization") version "1.4.10"
}
group = "com.example.ktor.serialization"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
    jcenter()
    maven {
        url = uri("https://dl.bintray.com/kotlin/ktor")
    }
    maven {
        url = uri("https://dl.bintray.com/kotlin/kotlinx")
    }
}
dependencies {
    testImplementation(kotlin("test-junit5"))
    implementation("io.ktor:ktor-server-netty:1.4.1")
    implementation("io.ktor:ktor-html-builder:1.4.1")
    implementation("io.ktor:ktor-serialization:1.4.1")
    implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.2")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
    implementation("ch.qos.logback:logback-classic:1.2.3")
}
tasks.withType<KotlinCompile>() {
    kotlinOptions.jvmTarget = "11"
}
application {
    mainClassName = "ServerKt"
}
  

Ответ №1:

Как сказано в stacktrace this is not supported yet. , так что это может произойти когда-нибудь.

Однако для такого случая все еще возможен обходной путь. Проблема связана с Ktor, а не с сериализацией Kotlinx. Итак, вы можете сериализовать свои данные в формате JSON, а затем отправить их в качестве ответа, как здесь:

 fun Application.module(testing: Boolean = false) {
    install(ContentNegotiation) { json() }

    routing {
        get("/") {
            val data: List<Message> = listOf(
                DirectMessage("Hey, Joe!", "Joe"),
                BroadcastMessage("Hey, all!")
            )

            val string = Json.encodeToString(data)
            call.respondText(string, contentType = ContentType.Application.Json)
        }
    }
}

@Serializable
sealed class Message {
    abstract val content: String
}

@Serializable
data class BroadcastMessage(override val content: String) : Message()

@Serializable
data class DirectMessage(override val content: String, val recipient: String) : Message()
  

Ответ №2:

Причина в том, что у нас нет информации о конкретном типе и мы можем анализировать классы экземпляров только во время выполнения. Анализ пересечения типов и времени выполнения — непростая задача, и наверняка она будет очень неэффективной, что неприемлемо на стороне сервера.

Использование typeOf потенциально может помочь, но мы не проанализировали влияние такого изменения на производительность (включая профиль распределения). Другая причина заключается в том, что мы не знали об typeOf этом (он не существовал) и call.respond был разработан без него, так что это изменение наверняка будет решающим изменением.