Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use EnumSerializer for explicitly serializable enum instead of auto-generated #1851

Merged
merged 3 commits into from Sep 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 40 additions & 7 deletions core/commonMain/src/kotlinx/serialization/internal/Enums.kt
Expand Up @@ -7,6 +7,7 @@ package kotlinx.serialization.internal
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlin.jvm.Volatile

/*
* Descriptor used for explicitly serializable enums by the plugin.
Expand Down Expand Up @@ -49,20 +50,52 @@ internal class EnumDescriptor(
}
}

// Used for enums that are not explicitly serializable by the plugin
@OptIn(ExperimentalSerializationApi::class)
@InternalSerializationApi
internal fun <T : Enum<T>> createSimpleEnumSerializer(serialName: String, values: Array<T>): KSerializer<T> {
return EnumSerializer(serialName, values)
}

@OptIn(ExperimentalSerializationApi::class)
@InternalSerializationApi
internal fun <T : Enum<T>> createMarkedEnumSerializer(
serialName: String,
values: Array<T>,
names: Array<String?>,
annotations: Array<Array<Annotation>?>
): KSerializer<T> {
val descriptor = EnumDescriptor(serialName, values.size)
values.forEachIndexed { i, v ->
val elementName = names.getOrNull(i) ?: v.name
descriptor.addElement(elementName)
annotations.getOrNull(i)?.forEach {
descriptor.pushAnnotation(it)
}
}

return EnumSerializer(serialName, values, descriptor)
}

@PublishedApi
@OptIn(ExperimentalSerializationApi::class)
internal class EnumSerializer<T : Enum<T>>(
serialName: String,
private val values: Array<T>
) : KSerializer<T> {
private var overriddenDescriptor: SerialDescriptor? = null

override val descriptor: SerialDescriptor = buildSerialDescriptor(serialName, SerialKind.ENUM) {
values.forEach {
val fqn = "$serialName.${it.name}"
val enumMemberDescriptor = buildSerialDescriptor(fqn, StructureKind.OBJECT)
element(it.name, enumMemberDescriptor)
}
internal constructor(serialName: String, values: Array<T>, descriptor: SerialDescriptor) : this(serialName, values) {
overriddenDescriptor = descriptor
}

override val descriptor: SerialDescriptor by lazy {
overriddenDescriptor ?: createUnmarkedDescriptor(serialName)
}

private fun createUnmarkedDescriptor(serialName: String): SerialDescriptor {
val d = EnumDescriptor(serialName, values.size)
values.forEach { d.addElement(it.name) }
return d
}

override fun serialize(encoder: Encoder, value: T) {
Expand Down
25 changes: 25 additions & 0 deletions core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt
Expand Up @@ -22,11 +22,36 @@ class CachedSerializersTest {
@Serializable
abstract class Abstract

@Serializable
enum class SerializableEnum {A, B}

@SerialInfo
annotation class MyAnnotation(val x: Int)

@Serializable
enum class SerializableMarkedEnum {
@SerialName("first")
@MyAnnotation(1)
C,
@MyAnnotation(2)
D
}

@Test
fun testObjectSerializersAreSame() = noJsLegacy {
assertSame(Object.serializer(), Object.serializer())
}

@Test
fun testSerializableEnumSerializersAreSame() = noJsLegacy {
assertSame(SerializableEnum.serializer(), SerializableEnum.serializer())
}

@Test
fun testSerializableMarkedEnumSerializersAreSame() = noJsLegacy {
assertSame(SerializableMarkedEnum.serializer(), SerializableMarkedEnum.serializer())
}

@Test
fun testSealedSerializersAreSame() = noJsLegacy {
assertSame(SealedParent.serializer(), SealedParent.serializer())
Expand Down
Expand Up @@ -6,6 +6,7 @@ package kotlinx.serialization

import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.internal.EnumSerializer
import kotlinx.serialization.test.*
import kotlin.test.*

Expand Down Expand Up @@ -47,9 +48,45 @@ class SerializersLookupEnumTest {
@Serializable
enum class PlainEnum

@Serializable
enum class SerializableEnum { C, D }

@Serializable
enum class SerializableMarkedEnum { C, @SerialName("NotD") D }

@Test
fun testPlainEnum() {
assertEquals(PlainEnum.serializer(), serializer<PlainEnum>())
if (isJsLegacy()) {
assertSame(PlainEnum.serializer()::class, serializer<PlainEnum>()::class)
} else {
assertSame(PlainEnum.serializer(), serializer<PlainEnum>())
}

if (!isJs()) {
assertIs<EnumSerializer<PlainEnum>>(serializer<PlainEnum>())
}
}

@Test
fun testSerializableEnumSerializer() {
assertIs<EnumSerializer<SerializableEnum>>(SerializableEnum.serializer())

if (isJsLegacy()) {
assertSame(SerializableEnum.serializer()::class, serializer<SerializableEnum>()::class)
} else {
assertSame(SerializableEnum.serializer(), serializer<SerializableEnum>())
}
}

@Test
fun testSerializableMarkedEnumSerializer() {
assertIs<EnumSerializer<SerializableMarkedEnum>>(SerializableMarkedEnum.serializer())

if (isJsLegacy()) {
assertSame(SerializableMarkedEnum.serializer()::class, serializer<SerializableMarkedEnum>()::class)
} else {
assertSame(SerializableMarkedEnum.serializer(), serializer<SerializableMarkedEnum>())
}
}

@Test
Expand Down
Expand Up @@ -127,7 +127,7 @@ class EnumSerializationTest : JsonTestBase() {
fun testStructurallyEqualDescriptors() {
val libraryGenerated = Wrapper.serializer().descriptor.getElementDescriptor(0)
val codeGenerated = MyEnum2.serializer().descriptor
assertNotEquals(libraryGenerated::class, codeGenerated::class)
assertEquals(libraryGenerated::class, codeGenerated::class)
libraryGenerated.assertDescriptorEqualsTo(codeGenerated)
}
}