Skip to content

Commit

Permalink
Use EnumSerializer for explicitly serializable enum instead of auto-g…
Browse files Browse the repository at this point in the history
…enerated

Resolves #683 #1372
  • Loading branch information
shanshin committed Sep 22, 2022
1 parent 4ed6cce commit 9658ea7
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 9 deletions.
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)
}
}

0 comments on commit 9658ea7

Please sign in to comment.