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 Jul 12, 2022
1 parent df3a161 commit fe86ca1
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 16 deletions.
49 changes: 42 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,54 @@ 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)
}

// TODO we can create another class for factories, it may be a copy-paste of this class or a subclass for some `AbstractEnumSerializer` with common `serialize`, `deserialize` and `toString` functions
@PublishedApi
@OptIn(ExperimentalSerializationApi::class)
internal class EnumSerializer<T : Enum<T>>(
serialName: String,
private val values: Array<T>
) : KSerializer<T> {
@Volatile
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 @@ -20,11 +20,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 @@ -50,9 +51,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 All @@ -64,13 +101,7 @@ class SerializersLookupEnumTest {
@Test
fun testEnumExternalClass() {
assertIs<EnumExternalClassSerializer>(EnumExternalClass.serializer())

if (isJvm()) {
assertIs<EnumExternalClassSerializer>(serializer<EnumExternalClass>())
} else if (isJsIr() || isNative()) {
// FIXME serializer<EnumWithClassSerializer> is broken for K/JS and K/Native. Remove `assertFails` after fix
assertFails { serializer<EnumExternalClass>() }
}
assertIs<EnumExternalClassSerializer>(serializer<EnumExternalClass>())
}

@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 fe86ca1

Please sign in to comment.