commonTest.kotlinx.serialization.features.SealedClassesSerializationTest.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.builtins.*
import kotlinx.serialization.features.sealed.SealedChild
import kotlinx.serialization.features.sealed.SealedParent
import kotlinx.serialization.internal.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlinx.serialization.test.*
import kotlin.test.*
class SealedClassesSerializationTest : JsonTestBase() {
@Serializable
sealed class SealedProtocol {
@Serializable
@SerialName("SealedProtocol.StringMessage")
data class StringMessage(val description: String, val message: String) : SealedProtocol()
@Serializable
@SerialName("SealedProtocol.IntMessage")
data class IntMessage(val description: String, val message: Int) : SealedProtocol()
@Serializable
@SerialName("SealedProtocol.ErrorMessage")
data class ErrorMessage(val error: String) : SealedProtocol()
@SerialName("EOF")
@Serializable
object EOF : SealedProtocol()
}
@Serializable
sealed class ProtocolWithAbstractClass {
@Serializable
@SerialName("ProtocolWithAbstractClass.Message")
abstract class Message : ProtocolWithAbstractClass() {
@Serializable
@SerialName("ProtocolWithAbstractClass.Message.StringMessage")
data class StringMessage(val description: String, val message: String) : Message()
@Serializable
@SerialName("ProtocolWithAbstractClass.Message.IntMessage")
data class IntMessage(val description: String, val message: Int) : Message()
}
@Serializable
@SerialName("ProtocolWithAbstractClass.ErrorMessage")
data class ErrorMessage(val error: String) : ProtocolWithAbstractClass()
@SerialName("EOF")
@Serializable
object EOF : ProtocolWithAbstractClass()
}
@Serializable
sealed class ProtocolWithSealedClass {
@Serializable
@SerialName("ProtocolWithSealedClass.Message")
sealed class Message : ProtocolWithSealedClass() {
@Serializable
@SerialName("ProtocolWithSealedClass.Message.StringMessage")
data class StringMessage(val description: String, val message: String) : Message()
@Serializable
@SerialName("ProtocolWithSealedClass.Message.IntMessage")
data class IntMessage(val description: String, val message: Int) : Message()
}
@Serializable
@SerialName("ProtocolWithSealedClass.ErrorMessage")
data class ErrorMessage(val error: String) : ProtocolWithSealedClass()
@SerialName("EOF")
@Serializable
object EOF : ProtocolWithSealedClass()
}
@Serializable
sealed class ProtocolWithGenericClass {
@Serializable
@SerialName("ProtocolWithGenericClass.Message")
data class Message(val description: String, val message: T) : ProtocolWithGenericClass()
@Serializable
@SerialName("ProtocolWithGenericClass.ErrorMessage")
data class ErrorMessage(val error: String) : ProtocolWithGenericClass()
@SerialName("EOF")
@Serializable
object EOF : ProtocolWithGenericClass()
}
private val ManualSerializer: KSerializer = SealedClassSerializer(
"SimpleSealed",
SimpleSealed::class,
arrayOf(SimpleSealed.SubSealedA::class, SimpleSealed.SubSealedB::class),
arrayOf(SimpleSealed.SubSealedA.serializer(), SimpleSealed.SubSealedB.serializer())
)
@Serializable
data class SealedHolder(val s: SimpleSealed)
@Serializable
data class SealedBoxHolder(val b: Box)
private val arrayJson = Json { useArrayPolymorphism = true }
private val json = Json
@Test
fun manualSerializer() {
val message = json.encodeToString(
ManualSerializer,
SimpleSealed.SubSealedB(42)
)
assertEquals("{\"type\":\"kotlinx.serialization.SimpleSealed.SubSealedB\",\"i\":42}", message)
}
@Test
fun onTopLevel() {
val arrayMessage = arrayJson.encodeToString(
SimpleSealed.serializer(),
SimpleSealed.SubSealedB(42)
)
val message = json.encodeToString(
SimpleSealed.serializer(),
SimpleSealed.SubSealedB(42)
)
assertEquals("{\"type\":\"kotlinx.serialization.SimpleSealed.SubSealedB\",\"i\":42}", message)
assertEquals("[\"kotlinx.serialization.SimpleSealed.SubSealedB\",{\"i\":42}]", arrayMessage)
}
@Test
fun insideClass() {
assertJsonFormAndRestored(
SealedHolder.serializer(),
SealedHolder(SimpleSealed.SubSealedA("foo")),
"""{"s":{"type":"kotlinx.serialization.SimpleSealed.SubSealedA","s":"foo"}}""",
json
)
}
@Test
fun insideGeneric() {
assertJsonFormAndRestored(
Box.serializer(SimpleSealed.serializer()),
Box(SimpleSealed.SubSealedA("foo")),
"""{"boxed":{"type":"kotlinx.serialization.SimpleSealed.SubSealedA","s":"foo"}}""",
json
)
assertJsonFormAndRestored(
SealedBoxHolder.serializer(),
SealedBoxHolder(Box(SimpleSealed.SubSealedA("foo"))),
"""{"b":{"boxed":{"type":"kotlinx.serialization.SimpleSealed.SubSealedA","s":"foo"}}}""",
json
)
}
@Test
fun complexProtocol() {
val messages = listOf(
SealedProtocol.StringMessage("string message", "foo"),
SealedProtocol.IntMessage("int message", 42),
SealedProtocol.ErrorMessage("requesting termination"),
SealedProtocol.EOF
)
val expected =
"""[{"type":"SealedProtocol.StringMessage","description":"string message","message":"foo"},{"type":"SealedProtocol.IntMessage","description":"int message","message":42},{"type":"SealedProtocol.ErrorMessage","error":"requesting termination"},{"type":"EOF"}]"""
assertJsonFormAndRestored(ListSerializer(SealedProtocol.serializer()), messages, expected, json)
}
@Test
fun protocolWithAbstractClass() {
val messages = listOf(
ProtocolWithAbstractClass.Message.StringMessage("string message", "foo"),
ProtocolWithAbstractClass.Message.IntMessage("int message", 42),
ProtocolWithAbstractClass.ErrorMessage("requesting termination"),
ProtocolWithAbstractClass.EOF
)
val abstractContext = SerializersModule {
polymorphic(ProtocolWithAbstractClass::class) {
subclass(ProtocolWithAbstractClass.Message.IntMessage.serializer())
subclass(ProtocolWithAbstractClass.Message.StringMessage.serializer())
}
polymorphic(ProtocolWithAbstractClass.Message::class) {
subclass(ProtocolWithAbstractClass.Message.IntMessage.serializer())
subclass(ProtocolWithAbstractClass.Message.StringMessage.serializer())
}
}
val json = Json {
serializersModule = abstractContext
}
val expected =
"""[{"type":"ProtocolWithAbstractClass.Message.StringMessage","description":"string message","message":"foo"},{"type":"ProtocolWithAbstractClass.Message.IntMessage","description":"int message","message":42},{"type":"ProtocolWithAbstractClass.ErrorMessage","error":"requesting termination"},{"type":"EOF"}]"""
assertJsonFormAndRestored(ListSerializer(ProtocolWithAbstractClass.serializer()), messages, expected, json)
}
@Test
fun protocolWithSealedClass() {
val messages = listOf(
ProtocolWithSealedClass.Message.StringMessage("string message", "foo"),
ProtocolWithSealedClass.Message.IntMessage("int message", 42),
ProtocolWithSealedClass.ErrorMessage("requesting termination"),
ProtocolWithSealedClass.EOF
)
val expected =
"""[{"type":"ProtocolWithSealedClass.Message.StringMessage","description":"string message","message":"foo"},{"type":"ProtocolWithSealedClass.Message.IntMessage","description":"int message","message":42},{"type":"ProtocolWithSealedClass.ErrorMessage","error":"requesting termination"},{"type":"EOF"}]"""
assertJsonFormAndRestored(ListSerializer(ProtocolWithSealedClass.serializer()), messages, expected, json)
}
@Test
fun partOfProtocolWithSealedClass() {
val messages = listOf(
ProtocolWithSealedClass.Message.StringMessage("string message", "foo"),
ProtocolWithSealedClass.Message.IntMessage("int message", 42)
)
val expected =
"""[{"type":"ProtocolWithSealedClass.Message.StringMessage","description":"string message","message":"foo"},{"type":"ProtocolWithSealedClass.Message.IntMessage","description":"int message","message":42}]"""
assertJsonFormAndRestored(ListSerializer(ProtocolWithSealedClass.serializer()), messages, expected, json)
assertJsonFormAndRestored(ListSerializer(ProtocolWithSealedClass.Message.serializer()), messages, expected, json)
}
@Test
fun protocolWithGenericClass() {
val messages = listOf(
ProtocolWithGenericClass.Message("string message", "foo"),
ProtocolWithGenericClass.Message("int message", 42),
ProtocolWithGenericClass.ErrorMessage("requesting termination"),
ProtocolWithGenericClass.EOF
)
val expected =
"""[["ProtocolWithGenericClass.Message",{"description":"string message","message":["kotlin.String","foo"]}],["ProtocolWithGenericClass.Message",{"description":"int message","message":["kotlin.Int",42]}],["ProtocolWithGenericClass.ErrorMessage",{"error":"requesting termination"}],["EOF",{}]]"""
val json = Json {
useArrayPolymorphism = true
serializersModule = SerializersModule {
polymorphic(Any::class) {
subclass(Int::class)
subclass(String::class)
}
}
}
assertJsonFormAndRestored(ListSerializer(ProtocolWithGenericClass.serializer()), messages, expected, json)
}
@Test
fun testSerializerLookupForSealedClass() {
val resSer = serializer()
assertEquals(SealedProtocol::class, (resSer as AbstractPolymorphicSerializer).baseClass)
}
@Test
fun testClassesFromDifferentFiles() {
assertJsonFormAndRestored(SealedParent.serializer(), SealedChild(5), """{"type":"first child","i":1,"j":5}""")
}
}