Skip to content

Commit 2e960d0

Browse files
authored
Best-effort kotlin reflect avoidance in serializer(Type)
Fixes Kotlin#1819
1 parent 63fe3ff commit 2e960d0

File tree

5 files changed

+60
-36
lines changed

5 files changed

+60
-36
lines changed

core/commonMain/src/kotlinx/serialization/Annotations.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ public annotation class Polymorphic
294294
* * Implementing [SerialDescriptor] interfaces
295295
* * Not-yet-stable serialization formats that require additional polishing
296296
*/
297+
@MustBeDocumented
297298
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS)
298299
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
299300
public annotation class ExperimentalSerializationApi
@@ -305,6 +306,7 @@ public annotation class ExperimentalSerializationApi
305306
* and will be changed without any warnings or migration aids.
306307
* If you cannot avoid using internal API to solve your problem, please report your use-case to serialization's issue tracker.
307308
*/
309+
@MustBeDocumented
308310
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS)
309311
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
310312
public annotation class InternalSerializationApi

core/commonMain/src/kotlinx/serialization/Serializers.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ internal fun <T : Any> SerializersModule.reflectiveOrContextual(kClass: KClass<T
126126
return kClass.serializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers)
127127
}
128128

129-
130129
/**
131130
* Retrieves a [KSerializer] for the given [KClass].
132131
* The given class must be annotated with [Serializable] or be one of the built-in types.
@@ -137,7 +136,7 @@ internal fun <T : Any> SerializersModule.reflectiveOrContextual(kClass: KClass<T
137136
*
138137
* The recommended way to retrieve the serializer is inline [serializer] function and [`serializer(KType)`][serializer]
139138
*
140-
* This API is not guaranteed to work consistent across different platforms or
139+
* This API is not guaranteed to work consistently across different platforms or
141140
* to work in cases that slightly differ from "plain @Serializable class" and have platform and reflection specific limitations.
142141
*
143142
* ### Constraints
@@ -161,11 +160,11 @@ public fun <T : Any> KClass<T>.serializer(): KSerializer<T> = serializerOrNull()
161160
* and it is not recommended to use this method for anything, but last-ditch resort, e.g.
162161
* when all type info is lost, your application has crashed and it is the final attempt to log or send some serializable data.
163162
*
164-
* This API is not guaranteed to work consistent across different platforms or
163+
* This API is not guaranteed to work consistently across different platforms or
165164
* to work in cases that slightly differ from "plain @Serializable class".
166165
*
167166
* ### Constraints
168-
* This paragraph explains known (but not all!) constraints of the `serializer()` implementation.
167+
* This paragraph explains known (but not all!) constraints of the `serializerOrNull()` implementation.
169168
* Please note that they are not bugs, but implementation restrictions that we cannot workaround.
170169
*
171170
* * This method may behave differently on JVM, JS and Native because of runtime reflection differences

core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public fun serializerOrNull(type: Type): KSerializer<Any>? = EmptySerializersMod
5656
*/
5757
@ExperimentalSerializationApi
5858
public fun SerializersModule.serializer(type: Type): KSerializer<Any> =
59-
serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = true) ?: type.kclass().serializerNotRegistered()
59+
serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = true) ?: type.prettyClass().serializerNotRegistered()
6060

6161
/**
6262
* Retrieves serializer for the given reflective Java [type] using
@@ -113,8 +113,7 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing
113113
// probably we should deprecate this method because it can't differ nullable vs non-nullable types
114114
// since it uses Java TypeToken, not Kotlin one
115115
val varargs = argsSerializers.map { it as KSerializer<Any?> }
116-
(rootClass.kotlin.constructSerializerForGivenTypeArgs(*(varargs.toTypedArray())) as? KSerializer<Any>)
117-
?: reflectiveOrContextual(rootClass.kotlin as KClass<Any>, varargs)
116+
reflectiveOrContextual(rootClass as Class<Any>, varargs)
118117
}
119118
}
120119
}
@@ -130,10 +129,17 @@ private fun SerializersModule.typeSerializer(type: Class<*>, failOnMissingTypeAr
130129
val arraySerializer = ArraySerializer(eType.kotlin as KClass<Any>, s)
131130
arraySerializer as KSerializer<Any>
132131
} else {
133-
reflectiveOrContextual(type.kotlin as KClass<Any>, emptyList())
132+
reflectiveOrContextual(type as Class<Any>, emptyList())
134133
}
135134
}
136135

136+
@OptIn(ExperimentalSerializationApi::class)
137+
private fun <T : Any> SerializersModule.reflectiveOrContextual(jClass: Class<T>, typeArgumentsSerializers: List<KSerializer<Any?>>): KSerializer<T>? {
138+
jClass.constructSerializerForGivenTypeArgs(*typeArgumentsSerializers.toTypedArray())?.let { return it }
139+
val kClass = jClass.kotlin
140+
return kClass.builtinSerializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers)
141+
}
142+
137143
@OptIn(ExperimentalSerializationApi::class)
138144
private fun SerializersModule.genericArraySerializer(
139145
type: GenericArrayType,
@@ -154,11 +160,10 @@ private fun SerializersModule.genericArraySerializer(
154160
return ArraySerializer(kclass, serializer) as KSerializer<Any>
155161
}
156162

157-
private fun Type.kclass(): KClass<*> = when (val it = this) {
158-
is KClass<*> -> it
159-
is Class<*> -> it.kotlin
160-
is ParameterizedType -> it.rawType.kclass()
161-
is WildcardType -> it.upperBounds.first().kclass()
162-
is GenericArrayType -> it.genericComponentType.kclass()
163+
private fun Type.prettyClass(): Class<*> = when (val it = this) {
164+
is Class<*> -> it
165+
is ParameterizedType -> it.rawType.prettyClass()
166+
is WildcardType -> it.upperBounds.first().prettyClass()
167+
is GenericArrayType -> it.genericComponentType.prettyClass()
163168
else -> throw IllegalArgumentException("typeToken should be an instance of Class<?>, GenericArray, ParametrizedType or WildcardType, but actual type is $it ${it::class}")
164169
}

core/jvmMain/src/kotlinx/serialization/internal/Platform.kt

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,34 @@ internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KCl
2828

2929
internal actual fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing = serializerNotRegistered()
3030

31-
@Suppress("UNCHECKED_CAST")
31+
internal fun Class<*>.serializerNotRegistered(): Nothing {
32+
throw SerializationException(
33+
"Serializer for class '${simpleName}' is not found.\n" +
34+
"Mark the class as @Serializable or provide the serializer explicitly."
35+
)
36+
}
37+
3238
internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? {
33-
val jClass = this.java
34-
if (jClass.isEnum && jClass.isNotAnnotated()) {
35-
return jClass.createEnumSerializer()
39+
return java.constructSerializerForGivenTypeArgs(*args)
40+
}
41+
42+
@Suppress("UNCHECKED_CAST")
43+
internal fun <T: Any> Class<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? {
44+
if (isEnum && isNotAnnotated()) {
45+
return createEnumSerializer()
3646
}
37-
if (jClass.isInterface) {
47+
if (isInterface) {
3848
return interfaceSerializer()
3949
}
4050
// Search for serializer defined on companion object.
41-
val serializer = invokeSerializerOnCompanion<T>(jClass, *args)
51+
val serializer = invokeSerializerOnCompanion<T>(this, *args)
4252
if (serializer != null) return serializer
4353
// Check whether it's serializable object
44-
findObjectSerializer(jClass)?.let { return it }
54+
findObjectSerializer()?.let { return it }
4555
// Search for default serializer if no serializer is defined in companion object.
4656
// It is required for named companions
4757
val fromNamedCompanion = try {
48-
jClass.declaredClasses.singleOrNull { it.simpleName == ("\$serializer") }
58+
declaredClasses.singleOrNull { it.simpleName == ("\$serializer") }
4959
?.getField("INSTANCE")?.get(null) as? KSerializer<T>
5060
} catch (e: NoSuchFieldException) {
5161
null
@@ -63,31 +73,30 @@ private fun <T: Any> Class<T>.isNotAnnotated(): Boolean {
6373
getAnnotation(Polymorphic::class.java) == null
6474
}
6575

66-
private fun <T: Any> KClass<T>.polymorphicSerializer(): KSerializer<T>? {
76+
private fun <T: Any> Class<T>.polymorphicSerializer(): KSerializer<T>? {
6777
/*
6878
* Last resort: check for @Polymorphic or Serializable(with = PolymorphicSerializer::class)
6979
* annotations.
7080
*/
71-
val jClass = java
72-
if (jClass.getAnnotation(Polymorphic::class.java) != null) {
73-
return PolymorphicSerializer(this)
81+
if (getAnnotation(Polymorphic::class.java) != null) {
82+
return PolymorphicSerializer(this.kotlin)
7483
}
75-
val serializable = jClass.getAnnotation(Serializable::class.java)
84+
val serializable = getAnnotation(Serializable::class.java)
7685
if (serializable != null && serializable.with == PolymorphicSerializer::class) {
77-
return PolymorphicSerializer(this)
86+
return PolymorphicSerializer(this.kotlin)
7887
}
7988
return null
8089
}
8190

82-
private fun <T: Any> KClass<T>.interfaceSerializer(): KSerializer<T>? {
91+
private fun <T: Any> Class<T>.interfaceSerializer(): KSerializer<T>? {
8392
/*
8493
* Interfaces are @Polymorphic by default.
8594
* Check if it has no annotations or `@Serializable(with = PolymorphicSerializer::class)`,
8695
* otherwise bailout.
8796
*/
88-
val serializable = java.getAnnotation(Serializable::class.java)
97+
val serializable = getAnnotation(Serializable::class.java)
8998
if (serializable == null || serializable.with == PolymorphicSerializer::class) {
90-
return PolymorphicSerializer(this)
99+
return PolymorphicSerializer(this.kotlin)
91100
}
92101
return null
93102
}
@@ -122,15 +131,15 @@ private fun <T : Any> Class<T>.createEnumSerializer(): KSerializer<T>? {
122131
return EnumSerializer(canonicalName, constants as Array<out Enum<*>>) as? KSerializer<T>
123132
}
124133

125-
private fun <T : Any> findObjectSerializer(jClass: Class<T>): KSerializer<T>? {
134+
private fun <T : Any> Class<T>.findObjectSerializer(): KSerializer<T>? {
126135
// Check it is an object without using kotlin-reflect
127136
val field =
128-
jClass.declaredFields.singleOrNull { it.name == "INSTANCE" && it.type == jClass && Modifier.isStatic(it.modifiers) }
137+
declaredFields.singleOrNull { it.name == "INSTANCE" && it.type == this && Modifier.isStatic(it.modifiers) }
129138
?: return null
130139
// Retrieve its instance and call serializer()
131140
val instance = field.get(null)
132141
val method =
133-
jClass.methods.singleOrNull { it.name == "serializer" && it.parameterTypes.isEmpty() && it.returnType == KSerializer::class.java }
142+
methods.singleOrNull { it.name == "serializer" && it.parameterTypes.isEmpty() && it.returnType == KSerializer::class.java }
134143
?: return null
135144
val result = method.invoke(instance)
136145
@Suppress("UNCHECKED_CAST")

formats/json/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import kotlin.test.*
1818

1919
class SerializerByTypeTest {
2020

21-
val json = Json { }
21+
private val json = Json
2222

2323
@Serializable
2424
data class Box<out T>(val a: T)
@@ -56,6 +56,12 @@ class SerializerByTypeTest {
5656
assertSerializedWithType(IntBoxToken, """{"a":42}""", b)
5757
}
5858

59+
@Test
60+
fun testNestedGenericParameter() {
61+
val b = Box(Box(239))
62+
assertSerializedWithType(typeTokenOf<Box<Box<Int>>>(), """{"a":{"a":239}}""", b)
63+
}
64+
5965
@Test
6066
fun testArray() {
6167
val myArr = arrayOf("a", "b", "c")
@@ -156,7 +162,6 @@ class SerializerByTypeTest {
156162
val myTriple = Triple("1", 2, Box(42))
157163
val token = typeTokenOf<Triple<String, Int, Box<Int>>>()
158164
assertSerializedWithType(token, """{"first":"1","second":2,"third":{"a":42}}""", myTriple)
159-
160165
}
161166

162167
@Test
@@ -273,5 +278,9 @@ class SerializerByTypeTest {
273278
assertFailsWithMessage<SerializationException>("for class 'NonSerializable'") {
274279
serializer(typeTokenOf<kotlinx.serialization.Box<NonSerializable>>())
275280
}
281+
282+
assertFailsWithMessage<SerializationException>("for class 'NonSerializable'") {
283+
serializer(typeTokenOf<Array<NonSerializable>>())
284+
}
276285
}
277286
}

0 commit comments

Comments
 (0)