Skip to content

Commit

Permalink
Introduce java.lang.Class.serializerOrNull() that is equivalent of KC…
Browse files Browse the repository at this point in the history
…lass.serializerOrNull(), but attempts not to instantiate KClass when possible

Fixes #1819
  • Loading branch information
qwwdfsad committed Jan 12, 2022
1 parent 676d92f commit e8f51f6
Show file tree
Hide file tree
Showing 6 changed files with 29 additions and 6 deletions.
1 change: 1 addition & 0 deletions core/api/kotlinx-serialization-core.api
Expand Up @@ -117,6 +117,7 @@ public final class kotlinx/serialization/SerializersKt {
public static final fun serializer (Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlinx/serialization/modules/SerializersModule;Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer;
public static final fun serializerOrNull (Ljava/lang/Class;)Lkotlinx/serialization/KSerializer;
public static final fun serializerOrNull (Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer;
public static final fun serializerOrNull (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer;
public static final fun serializerOrNull (Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer;
Expand Down
2 changes: 2 additions & 0 deletions core/commonMain/src/kotlinx/serialization/Annotations.kt
Expand Up @@ -294,6 +294,7 @@ public annotation class Polymorphic
* * Implementing [SerialDescriptor] interfaces
* * Not-yet-stable serialization formats that require additional polishing
*/
@MustBeDocumented
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
public annotation class ExperimentalSerializationApi
Expand All @@ -305,6 +306,7 @@ public annotation class ExperimentalSerializationApi
* and will be changed without any warnings or migration aids.
* If you cannot avoid using internal API to solve your problem, please report your use-case to serialization's issue tracker.
*/
@MustBeDocumented
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS)
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
public annotation class InternalSerializationApi
6 changes: 3 additions & 3 deletions core/commonMain/src/kotlinx/serialization/Serializers.kt
Expand Up @@ -137,7 +137,7 @@ internal fun <T : Any> SerializersModule.reflectiveOrContextual(kClass: KClass<T
*
* The recommended way to retrieve the serializer is inline [serializer] function and [`serializer(KType)`][serializer]
*
* This API is not guaranteed to work consistent across different platforms or
* This API is not guaranteed to work consistently across different platforms or
* to work in cases that slightly differ from "plain @Serializable class" and have platform and reflection specific limitations.
*
* ### Constraints
Expand All @@ -161,11 +161,11 @@ public fun <T : Any> KClass<T>.serializer(): KSerializer<T> = serializerOrNull()
* and it is not recommended to use this method for anything, but last-ditch resort, e.g.
* when all type info is lost, your application has crashed and it is the final attempt to log or send some serializable data.
*
* This API is not guaranteed to work consistent across different platforms or
* This API is not guaranteed to work consistently across different platforms or
* to work in cases that slightly differ from "plain @Serializable class".
*
* ### Constraints
* This paragraph explains known (but not all!) constraints of the `serializer()` implementation.
* This paragraph explains known (but not all!) constraints of the `serializerOrNull()` implementation.
* Please note that they are not bugs, but implementation restrictions that we cannot workaround.
*
* * This method may behave differently on JVM, JS and Native because of runtime reflection differences
Expand Down
13 changes: 13 additions & 0 deletions core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt
Expand Up @@ -74,6 +74,19 @@ public fun SerializersModule.serializer(type: Type): KSerializer<Any> =
public fun SerializersModule.serializerOrNull(type: Type): KSerializer<Any>? =
serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = false)

/**
* Retrieves a [KSerializer] for the given [java.lang.Class] instance of the Kotlin type or returns `null` if none is found.
* The given class must be annotated with [Serializable] or be one of the built-in types.
*
* The API avoids instantiation of the correspnding [KClass][Class.kotlin] on the best-effort basis.
*
* This is a [Class] counterpart of [KClass.serializerOrNull] with all the restrictions the original function implies,
* including the general recommendation to avoid uses of this API.
*/
@InternalSerializationApi
public fun <T : Any> Class<T>.serializerOrNull(): KSerializer<T>? =
constructSerializerForGivenTypeArgs() ?: kotlin.builtinSerializerOrNull()

@OptIn(ExperimentalSerializationApi::class)
private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissingTypeArgSerializer: Boolean = true): KSerializer<Any>? =
when (type) {
Expand Down
Expand Up @@ -33,7 +33,7 @@ internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vara
}

@Suppress("UNCHECKED_CAST")
private fun <T: Any> Class<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? {
internal fun <T: Any> Class<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? {
if (isEnum && isNotAnnotated()) {
return createEnumSerializer()
}
Expand Down
Expand Up @@ -18,7 +18,7 @@ import kotlin.test.*

class SerializerByTypeTest {

val json = Json { }
private val json = Json

@Serializable
data class Box<out T>(val a: T)
Expand Down Expand Up @@ -61,6 +61,7 @@ class SerializerByTypeTest {
val myArr = arrayOf("a", "b", "c")
val token = myArr::class.java
assertSerializedWithType(token, """["a","b","c"]""", myArr)
myArr::class.assertKClassSerializer()
}

@Test
Expand Down Expand Up @@ -156,7 +157,6 @@ class SerializerByTypeTest {
val myTriple = Triple("1", 2, Box(42))
val token = typeTokenOf<Triple<String, Int, Box<Int>>>()
assertSerializedWithType(token, """{"first":"1","second":2,"third":{"a":42}}""", myTriple)

}

@Test
Expand All @@ -175,20 +175,23 @@ class SerializerByTypeTest {
fun testNamedCompanion() {
val namedCompanion = WithNamedCompanion(1)
assertSerializedWithType(WithNamedCompanion::class.java, """{"a":1}""", namedCompanion)
WithNamedCompanion::class.assertKClassSerializer()
}

@Test
fun testPrimitive() {
val token = typeTokenOf<Int>()
val serial = serializer(token)
assertSame(Int.serializer() as KSerializer<*>, serial)
Int::class.assertKClassSerializer()
}

@Test
fun testObject() {
val token = typeTokenOf<SerializableObject>()
val serial = serializer(token)
assertEquals(SerializableObject.serializer().descriptor, serial.descriptor)
SerializableObject::class.assertKClassSerializer()
}

@Suppress("UNCHECKED_CAST")
Expand Down Expand Up @@ -274,4 +277,8 @@ class SerializerByTypeTest {
serializer(typeTokenOf<kotlinx.serialization.Box<NonSerializable>>())
}
}

private fun <T: Any> KClass<T>.assertKClassSerializer() {
assertEquals(serializerOrNull(), java.serializerOrNull())
}
}

0 comments on commit e8f51f6

Please sign in to comment.