Skip to content

Commit fe86ca1

Browse files
committed
Use EnumSerializer for explicitly serializable enum instead of auto-generated
Resolves #683 #1372
1 parent df3a161 commit fe86ca1

File tree

4 files changed

+107
-16
lines changed

4 files changed

+107
-16
lines changed

core/commonMain/src/kotlinx/serialization/internal/Enums.kt

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package kotlinx.serialization.internal
77
import kotlinx.serialization.*
88
import kotlinx.serialization.descriptors.*
99
import kotlinx.serialization.encoding.*
10+
import kotlin.jvm.Volatile
1011

1112
/*
1213
* Descriptor used for explicitly serializable enums by the plugin.
@@ -49,20 +50,54 @@ internal class EnumDescriptor(
4950
}
5051
}
5152

52-
// Used for enums that are not explicitly serializable by the plugin
53+
@OptIn(ExperimentalSerializationApi::class)
54+
@InternalSerializationApi
55+
internal fun <T : Enum<T>> createSimpleEnumSerializer(serialName: String, values: Array<T>): KSerializer<T> {
56+
return EnumSerializer(serialName, values)
57+
}
58+
59+
@OptIn(ExperimentalSerializationApi::class)
60+
@InternalSerializationApi
61+
internal fun <T : Enum<T>> createMarkedEnumSerializer(
62+
serialName: String,
63+
values: Array<T>,
64+
names: Array<String?>,
65+
annotations: Array<Array<Annotation>?>
66+
): KSerializer<T> {
67+
val descriptor = EnumDescriptor(serialName, values.size)
68+
values.forEachIndexed { i, v ->
69+
val elementName = names.getOrNull(i) ?: v.name
70+
descriptor.addElement(elementName)
71+
annotations.getOrNull(i)?.forEach {
72+
descriptor.pushAnnotation(it)
73+
}
74+
}
75+
76+
return EnumSerializer(serialName, values, descriptor)
77+
}
78+
79+
// 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
5380
@PublishedApi
5481
@OptIn(ExperimentalSerializationApi::class)
5582
internal class EnumSerializer<T : Enum<T>>(
5683
serialName: String,
5784
private val values: Array<T>
5885
) : KSerializer<T> {
86+
@Volatile
87+
private var overriddenDescriptor: SerialDescriptor? = null
5988

60-
override val descriptor: SerialDescriptor = buildSerialDescriptor(serialName, SerialKind.ENUM) {
61-
values.forEach {
62-
val fqn = "$serialName.${it.name}"
63-
val enumMemberDescriptor = buildSerialDescriptor(fqn, StructureKind.OBJECT)
64-
element(it.name, enumMemberDescriptor)
65-
}
89+
internal constructor(serialName: String, values: Array<T>, descriptor: SerialDescriptor) : this(serialName, values) {
90+
overriddenDescriptor = descriptor
91+
}
92+
93+
override val descriptor: SerialDescriptor by lazy {
94+
overriddenDescriptor ?: createUnmarkedDescriptor(serialName)
95+
}
96+
97+
private fun createUnmarkedDescriptor(serialName: String): SerialDescriptor {
98+
val d = EnumDescriptor(serialName, values.size)
99+
values.forEach { d.addElement(it.name) }
100+
return d
66101
}
67102

68103
override fun serialize(encoder: Encoder, value: T) {

core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,36 @@ class CachedSerializersTest {
2020
@Serializable
2121
abstract class Abstract
2222

23+
@Serializable
24+
enum class SerializableEnum {A, B}
25+
26+
@SerialInfo
27+
annotation class MyAnnotation(val x: Int)
28+
29+
@Serializable
30+
enum class SerializableMarkedEnum {
31+
@SerialName("first")
32+
@MyAnnotation(1)
33+
C,
34+
@MyAnnotation(2)
35+
D
36+
}
37+
2338
@Test
2439
fun testObjectSerializersAreSame() = noJsLegacy {
2540
assertSame(Object.serializer(), Object.serializer())
2641
}
2742

43+
@Test
44+
fun testSerializableEnumSerializersAreSame() = noJsLegacy {
45+
assertSame(SerializableEnum.serializer(), SerializableEnum.serializer())
46+
}
47+
48+
@Test
49+
fun testSerializableMarkedEnumSerializersAreSame() = noJsLegacy {
50+
assertSame(SerializableMarkedEnum.serializer(), SerializableMarkedEnum.serializer())
51+
}
52+
2853
@Test
2954
fun testSealedSerializersAreSame() = noJsLegacy {
3055
assertSame(SealedParent.serializer(), SealedParent.serializer())

core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package kotlinx.serialization
66

77
import kotlinx.serialization.descriptors.*
88
import kotlinx.serialization.encoding.*
9+
import kotlinx.serialization.internal.EnumSerializer
910
import kotlinx.serialization.test.*
1011
import kotlin.test.*
1112

@@ -50,9 +51,45 @@ class SerializersLookupEnumTest {
5051
@Serializable
5152
enum class PlainEnum
5253

54+
@Serializable
55+
enum class SerializableEnum { C, D }
56+
57+
@Serializable
58+
enum class SerializableMarkedEnum { C, @SerialName("NotD") D }
59+
5360
@Test
5461
fun testPlainEnum() {
55-
assertEquals(PlainEnum.serializer(), serializer<PlainEnum>())
62+
if (isJsLegacy()) {
63+
assertSame(PlainEnum.serializer()::class, serializer<PlainEnum>()::class)
64+
} else {
65+
assertSame(PlainEnum.serializer(), serializer<PlainEnum>())
66+
}
67+
68+
if (!isJs()) {
69+
assertIs<EnumSerializer<PlainEnum>>(serializer<PlainEnum>())
70+
}
71+
}
72+
73+
@Test
74+
fun testSerializableEnumSerializer() {
75+
assertIs<EnumSerializer<SerializableEnum>>(SerializableEnum.serializer())
76+
77+
if (isJsLegacy()) {
78+
assertSame(SerializableEnum.serializer()::class, serializer<SerializableEnum>()::class)
79+
} else {
80+
assertSame(SerializableEnum.serializer(), serializer<SerializableEnum>())
81+
}
82+
}
83+
84+
@Test
85+
fun testSerializableMarkedEnumSerializer() {
86+
assertIs<EnumSerializer<SerializableMarkedEnum>>(SerializableMarkedEnum.serializer())
87+
88+
if (isJsLegacy()) {
89+
assertSame(SerializableMarkedEnum.serializer()::class, serializer<SerializableMarkedEnum>()::class)
90+
} else {
91+
assertSame(SerializableMarkedEnum.serializer(), serializer<SerializableMarkedEnum>())
92+
}
5693
}
5794

5895
@Test
@@ -64,13 +101,7 @@ class SerializersLookupEnumTest {
64101
@Test
65102
fun testEnumExternalClass() {
66103
assertIs<EnumExternalClassSerializer>(EnumExternalClass.serializer())
67-
68-
if (isJvm()) {
69-
assertIs<EnumExternalClassSerializer>(serializer<EnumExternalClass>())
70-
} else if (isJsIr() || isNative()) {
71-
// FIXME serializer<EnumWithClassSerializer> is broken for K/JS and K/Native. Remove `assertFails` after fix
72-
assertFails { serializer<EnumExternalClass>() }
73-
}
104+
assertIs<EnumExternalClassSerializer>(serializer<EnumExternalClass>())
74105
}
75106

76107
@Test

formats/json-tests/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ class EnumSerializationTest : JsonTestBase() {
127127
fun testStructurallyEqualDescriptors() {
128128
val libraryGenerated = Wrapper.serializer().descriptor.getElementDescriptor(0)
129129
val codeGenerated = MyEnum2.serializer().descriptor
130-
assertNotEquals(libraryGenerated::class, codeGenerated::class)
130+
assertEquals(libraryGenerated::class, codeGenerated::class)
131131
libraryGenerated.assertDescriptorEqualsTo(codeGenerated)
132132
}
133133
}

0 commit comments

Comments
 (0)