commonTest.kotlinx.serialization.features.PolymorphicOnClassesTest.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlinx-serialization-json
Show all versions of kotlinx-serialization-json
Kotlin multiplatform serialization runtime library
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.serialization.features
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.internal.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlinx.serialization.test.*
import kotlin.reflect.*
import kotlin.test.*
class PolymorphicOnClassesTest {
// this has implicit @Polymorphic
interface IMessage {
val body: String
}
// and this class too has implicit @Polymorphic
@Serializable
abstract class Message : IMessage {
abstract override val body: String
}
@Polymorphic
@Serializable
@SerialName("SimpleMessage") // to cut out package prefix
open class SimpleMessage : Message() {
override var body: String = "Simple"
}
@Serializable
@SerialName("DoubleSimpleMessage")
class DoubleSimpleMessage(val body2: String) : SimpleMessage()
@Serializable
@SerialName("MessageWithId")
open class MessageWithId(val id: Int, override val body: String) : Message()
@Serializable
class Holder(
val iMessage: IMessage,
val iMessageList: List,
val message: Message,
val msgSet: Set,
val simple: SimpleMessage,
// all above should be polymorphic
val withId: MessageWithId // but this not
)
private fun genTestData(): Holder {
var cnt = -1
fun gen(): MessageWithId {
cnt++
return MessageWithId(cnt, "Message #$cnt")
}
return Holder(gen(), listOf(gen(), gen()), gen(), setOf(SimpleMessage()), DoubleSimpleMessage("DoubleSimple"), gen())
}
@Suppress("UNCHECKED_CAST")
private val testModule = SerializersModule {
listOf(Message::class, IMessage::class, SimpleMessage::class).forEach { clz ->
polymorphic(clz as KClass) {
subclass(SimpleMessage.serializer())
subclass(DoubleSimpleMessage.serializer())
subclass(MessageWithId.serializer())
}
}
}
@Test
fun testEnablesImplicitlyOnInterfacesAndAbstractClasses() {
val json = Json {
prettyPrint = false
useArrayPolymorphism = true
serializersModule = testModule
encodeDefaults = true
}
val data = genTestData()
assertEquals(
"""{"iMessage":["MessageWithId",{"id":0,"body":"Message #0"}],""" +
""""iMessageList":[["MessageWithId",{"id":1,"body":"Message #1"}],""" +
"""["MessageWithId",{"id":2,"body":"Message #2"}]],"message":["MessageWithId",{"id":3,"body":"Message #3"}],""" +
""""msgSet":[["SimpleMessage",{"body":"Simple"}]],"simple":["DoubleSimpleMessage",{"body":"Simple",""" +
""""body2":"DoubleSimple"}],"withId":{"id":4,"body":"Message #4"}}""",
json.encodeToString(Holder.serializer(), data)
)
}
@Test
fun testDescriptor() {
val polyDesc = Holder.serializer().descriptor.elementDescriptors.first()
assertEquals(PolymorphicSerializer(IMessage::class).descriptor, polyDesc)
assertEquals(2, polyDesc.elementsCount)
assertEquals(PrimitiveKind.STRING, polyDesc.getElementDescriptor(0).kind)
}
private fun SerialDescriptor.inContext(module: SerializersModule): List = when (kind) {
PolymorphicKind.OPEN -> module.getPolymorphicDescriptors(this)
else -> error("Expected this function to be called on OPEN descriptor")
}
@Test
fun testResolvePolymorphicDescriptor() {
val polyDesc = Holder.serializer().descriptor.elementDescriptors.first() // iMessage: IMessage
assertEquals(PolymorphicKind.OPEN, polyDesc.kind)
val inheritors = polyDesc.inContext(testModule)
val names = listOf("SimpleMessage", "DoubleSimpleMessage", "MessageWithId").toSet()
assertEquals(names, inheritors.map(SerialDescriptor::serialName).toSet(), "Expected correct inheritor names")
assertTrue(inheritors.all { it.kind == StructureKind.CLASS }, "Expected all inheritors to be CLASS")
}
@Test
fun testDocSampleWithAllDistinct() {
fun allDistinctNames(descriptor: SerialDescriptor, module: SerializersModule) = when (descriptor.kind) {
is PolymorphicKind.OPEN -> module.getPolymorphicDescriptors(descriptor)
.map { it.elementNames.toList() }.flatten().toSet()
is SerialKind.CONTEXTUAL -> module.getContextualDescriptor(descriptor)?.elementNames?.toList().orEmpty().toSet()
else -> descriptor.elementNames.toSet()
}
val polyDesc = Holder.serializer().descriptor.elementDescriptors.first() // iMessage: IMessage
assertEquals(setOf("id", "body", "body2"), allDistinctNames(polyDesc, testModule))
assertEquals(setOf("id", "body"), allDistinctNames(MessageWithId.serializer().descriptor, testModule))
}
@Test
fun testSerializerLookupForInterface() {
// On JVM and JS IR it can be supported via reflection/runtime hacks
// on Native, unfortunately, only with intrinsics.
if (currentPlatform == Platform.NATIVE || currentPlatform == Platform.JS_LEGACY) return
val msgSer = serializer()
assertEquals(IMessage::class, (msgSer as AbstractPolymorphicSerializer).baseClass)
}
@Test
fun testSerializerLookupForAbstractClass() {
val absSer = serializer()
assertEquals(Message::class, (absSer as AbstractPolymorphicSerializer).baseClass)
}
}