From f451e43af7610d7e59a77c60c0e2bc9819c4997a Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Thu, 27 Oct 2022 18:45:16 +0300 Subject: [PATCH] Remove experimentality from serializer(java.lang.Type) function family (#2069) As it is needed for third-part converter libraries. Revamp docs for all overloads of serializer(). --- .../src/kotlinx/serialization/Serializers.kt | 97 +++++++++++++++---- .../modules/SerializersModuleBuilders.kt | 1 + .../kotlinx/serialization/SerializersJvm.kt | 81 ++++++++++------ .../serialization/SerializersLookupTest.kt | 27 ++++-- 4 files changed, 148 insertions(+), 58 deletions(-) diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt index e89fb33a0..01ea4507f 100644 --- a/core/commonMain/src/kotlinx/serialization/Serializers.kt +++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt @@ -18,15 +18,37 @@ import kotlin.reflect.* /** * Retrieves a serializer for the given type [T]. - * This method is a reified version of `serializer(KType)`. + * This overload is a reified version of `serializer(KType)`. + * + * This overload works with full type information, including type arguments and nullability, + * and is a recommended way to retrieve a serializer. + * For example, `serializer>()` returns [KSerializer] that is able + * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * + * Variance of [T]'s type arguments is not used by the serialization and is not taken into account. + * Star projections in [T]'s type arguments are prohibited. + * + * @throws SerializationException if serializer cannot be created (provided [T] or its type argument is not serializable). + * @throws IllegalArgumentException if any of [T]'s type arguments contains star projection */ public inline fun serializer(): KSerializer { return serializer(typeOf()).cast() } /** - * Retrieves serializer for the given type [T] from the current [SerializersModule] and, - * if not found, fallbacks to plain [serializer] method. + * Retrieves default serializer for the given type [T] and, + * if [T] is not serializable, fallbacks to [contextual][SerializersModule.getContextual] lookup. + * + * This overload works with full type information, including type arguments and nullability, + * and is a recommended way to retrieve a serializer. + * For example, `serializer>()` returns [KSerializer] that is able + * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * + * Variance of [T]'s type arguments is not used by the serialization and is not taken into account. + * Star projections in [T]'s type arguments are prohibited. + * + * @throws SerializationException if serializer cannot be created (provided [T] or its type argument is not serializable). + * @throws IllegalArgumentException if any of [T]'s type arguments contains star projection */ public inline fun SerializersModule.serializer(): KSerializer { return serializer(typeOf()).cast() @@ -34,38 +56,74 @@ public inline fun SerializersModule.serializer(): KSerializer { /** * Creates a serializer for the given [type]. - * [type] argument can be obtained with experimental [typeOf] method. - * @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable). + * [type] argument is usually obtained with [typeOf] method. + * + * This overload works with full type information, including type arguments and nullability, + * and is a recommended way to retrieve a serializer. + * For example, `serializer>>()` returns [KSerializer] that is able + * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * + * Variance of [type]'s type arguments is not used by the serialization and is not taken into account. + * Star projections in [type]'s arguments are prohibited. + * + * @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable). + * @throws IllegalArgumentException if any of [type]'s arguments contains star projection */ -@OptIn(ExperimentalSerializationApi::class) public fun serializer(type: KType): KSerializer = EmptySerializersModule().serializer(type) /** - * Creates a serializer for the given [type]. - * [type] argument can be obtained with experimental [typeOf] method. - * Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable). + * Creates a serializer for the given [type] if possible. + * [type] argument is usually obtained with [typeOf] method. + * + * This overload works with full type information, including type arguments and nullability, + * and is a recommended way to retrieve a serializer. + * For example, `serializerOrNull>>()` returns [KSerializer] that is able + * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * + * Variance of [type]'s arguments is not used by the serialization and is not taken into account. + * Star projections in [type]'s arguments are prohibited. + * + * @returns [KSerializer] for the given [type] or `null` if serializer cannot be created (given [type] or its type argument is not serializable). + * @throws IllegalArgumentException if any of [type]'s arguments contains star projection */ -@OptIn(ExperimentalSerializationApi::class) public fun serializerOrNull(type: KType): KSerializer? = EmptySerializersModule().serializerOrNull(type) /** - * Attempts to create a serializer for the given [type] and fallbacks to [contextual][SerializersModule.getContextual] - * lookup for non-serializable types. - * [type] argument can be obtained with experimental [typeOf] method. + * Retrieves default serializer for the given [type] and, + * if [type] is not serializable, fallbacks to [contextual][SerializersModule.getContextual] lookup. + * [type] argument is usually obtained with [typeOf] method. + * + * This overload works with full type information, including type arguments and nullability, + * and is a recommended way to retrieve a serializer. + * For example, `serializer>>()` returns [KSerializer] that is able + * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * + * Variance of [type]'s arguments is not used by the serialization and is not taken into account. + * Star projections in [type]'s arguments are prohibited. + * * @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable and is not registered in [this] module). + * @throws IllegalArgumentException if any of [type]'s arguments contains star projection */ -@OptIn(ExperimentalSerializationApi::class) public fun SerializersModule.serializer(type: KType): KSerializer = serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = true) ?: type.kclass() .platformSpecificSerializerNotRegistered() /** - * Attempts to create a serializer for the given [type] and fallbacks to [contextual][SerializersModule.getContextual] - * lookup for non-serializable types. - * [type] argument can be obtained with experimental [typeOf] method. - * Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable and is not registered in [this] module). + * Retrieves default serializer for the given [type] and, + * if [type] is not serializable, fallbacks to [contextual][SerializersModule.getContextual] lookup. + * [type] argument is usually obtained with [typeOf] method. + * + * This overload works with full type information, including type arguments and nullability, + * and is a recommended way to retrieve a serializer. + * For example, `serializerOrNull>>()` returns [KSerializer] that is able + * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * + * Variance of [type]'s arguments is not used by the serialization and is not taken into account. + * Star projections in [type]'s arguments are prohibited. + * + * @returns [KSerializer] for the given [type] or `null` if serializer cannot be created (given [type] or its type argument is not serializable and is not registered in [this] module). + * @throws IllegalArgumentException if any of [type]'s arguments contains star projection */ -@OptIn(ExperimentalSerializationApi::class) public fun SerializersModule.serializerOrNull(type: KType): KSerializer? = serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false) @@ -189,7 +247,6 @@ private fun KClass.builtinParametrizedSerializer( typeArguments: List, serializers: List>, ): KSerializer? { - // Array is not supported, see KT-32839 return when (this) { Collection::class, List::class, MutableList::class, ArrayList::class -> ArrayListSerializer(serializers[0]) HashSet::class -> HashSetSerializer(serializers[0]) diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt index 26c93769e..dfb9d819e 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt @@ -36,6 +36,7 @@ public inline fun SerializersModule(builderAction: SerializersModuleBuilder.() - /** * A [SerializersModule] which is empty and returns `null` from each method. */ +@Suppress("FunctionName") public fun EmptySerializersModule(): SerializersModule = @Suppress("DEPRECATION") EmptySerializersModule /** diff --git a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt index fb9a0cd10..e930eb2d4 100644 --- a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt +++ b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt @@ -17,65 +17,86 @@ import java.lang.reflect.* import kotlin.reflect.* /** - * Reflectively constructs a serializer for the given reflective Java [type]. - * [serializer] is intended to be used as an interoperability layer for libraries like GSON and Retrofit, - * that operate with reflective Java [Type] and cannot use [typeOf]. + * Reflectively retrieves a serializer for the given [type]. * - * For application-level serialization, it is recommended to use `serializer()` instead as it is aware of + * This overload is intended to be used as an interoperability layer for JVM-centric libraries, + * that operate with Java's type tokens and cannot use Kotlin's [KType] or [typeOf]. + * For application-level serialization, it is recommended to use `serializer()` or `serializer(KType)` instead as it is aware of * Kotlin-specific type information, such as nullability, sealed classes and object singletons. * + * Note that because [Type] does not contain any information about nullability, all created serializers + * work only with non-nullable data. + * + * Not all [Type] implementations are supported. + * [type] must be an instance of [Class], [GenericArrayType], [ParameterizedType] or [WildcardType]. + * * @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable). + * @throws IllegalArgumentException if an unsupported subclass of [Type] is provided. */ -@ExperimentalSerializationApi public fun serializer(type: Type): KSerializer = EmptySerializersModule().serializer(type) /** - * Reflectively constructs a serializer for the given reflective Java [type]. - * [serializer] is intended to be used as an interoperability layer for libraries like GSON and Retrofit, - * that operate with reflective Java [Type] and cannot use [typeOf]. + * Reflectively retrieves a serializer for the given [type]. * - * For application-level serialization, it is recommended to use `serializer()` instead as it is aware of + * This overload is intended to be used as an interoperability layer for JVM-centric libraries, + * that operate with Java's type tokens and cannot use Kotlin's [KType] or [typeOf]. + * For application-level serialization, it is recommended to use `serializer()` or `serializer(KType)` instead as it is aware of * Kotlin-specific type information, such as nullability, sealed classes and object singletons. * - * Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable). + * Note that because [Type] does not contain any information about nullability, all created serializers + * work only with non-nullable data. + * + * Not all [Type] implementations are supported. + * [type] must be an instance of [Class], [GenericArrayType], [ParameterizedType] or [WildcardType]. + * + * @return [KSerializer] for given [type] or `null` if serializer cannot be created (given [type] or its type argument is not serializable). + * @throws IllegalArgumentException if an unsupported subclass of [Type] is provided. */ -@ExperimentalSerializationApi public fun serializerOrNull(type: Type): KSerializer? = EmptySerializersModule().serializerOrNull(type) /** - * Retrieves serializer for the given reflective Java [type] using - * reflective construction and [contextual][SerializersModule.getContextual] lookup for non-serializable types. - * - * [serializer] is intended to be used as an interoperability layer for libraries like GSON and Retrofit, - * that operate with reflective Java [Type] and cannot use [typeOf]. + * Retrieves a serializer for the given [type] using + * reflective construction and [contextual][SerializersModule.getContextual] lookup as a fallback for non-serializable types. * - * For application-level serialization, it is recommended to use `serializer()` instead as it is aware of + * This overload is intended to be used as an interoperability layer for JVM-centric libraries, + * that operate with Java's type tokens and cannot use Kotlin's [KType] or [typeOf]. + * For application-level serialization, it is recommended to use `serializer()` or `serializer(KType)` instead as it is aware of * Kotlin-specific type information, such as nullability, sealed classes and object singletons. * + * Note that because [Type] does not contain any information about nullability, all created serializers + * work only with non-nullable data. + * + * Not all [Type] implementations are supported. + * [type] must be an instance of [Class], [GenericArrayType], [ParameterizedType] or [WildcardType]. + * * @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable). + * @throws IllegalArgumentException if an unsupported subclass of [Type] is provided. */ -@ExperimentalSerializationApi public fun SerializersModule.serializer(type: Type): KSerializer = serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = true) ?: type.prettyClass() .serializerNotRegistered() /** - * Retrieves serializer for the given reflective Java [type] using - * reflective construction and [contextual][SerializersModule.getContextual] lookup for non-serializable types. - * - * [serializer] is intended to be used as an interoperability layer for libraries like GSON and Retrofit, - * that operate with reflective Java [Type] and cannot use [typeOf]. + * Retrieves a serializer for the given [type] using + * reflective construction and [contextual][SerializersModule.getContextual] lookup as a fallback for non-serializable types. * - * For application-level serialization, it is recommended to use `serializer()` instead as it is aware of + * This overload is intended to be used as an interoperability layer for JVM-centric libraries, + * that operate with Java's type tokens and cannot use Kotlin's [KType] or [typeOf]. + * For application-level serialization, it is recommended to use `serializer()` or `serializer(KType)` instead as it is aware of * Kotlin-specific type information, such as nullability, sealed classes and object singletons. * - * Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable). + * Note that because [Type] does not contain any information about nullability, all created serializers + * work only with non-nullable data. + * + * Not all [Type] implementations are supported. + * [type] must be an instance of [Class], [GenericArrayType], [ParameterizedType] or [WildcardType]. + * + * @return [KSerializer] for given [type] or `null` if serializer cannot be created (given [type] or its type argument is not serializable). + * @throws IllegalArgumentException if an unsupported subclass of [Type] is provided. */ -@ExperimentalSerializationApi public fun SerializersModule.serializerOrNull(type: Type): KSerializer? = serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = false) -@OptIn(ExperimentalSerializationApi::class) private fun SerializersModule.serializerByJavaTypeImpl( type: Type, failOnMissingTypeArgSerializer: Boolean = true @@ -116,15 +137,13 @@ private fun SerializersModule.serializerByJavaTypeImpl( ) as KSerializer else -> { - // probably we should deprecate this method because it can't differ nullable vs non-nullable types - // since it uses Java TypeToken, not Kotlin one val varargs = argsSerializers.map { it as KSerializer } reflectiveOrContextual(rootClass as Class, varargs) } } } is WildcardType -> serializerByJavaTypeImpl(type.upperBounds.first()) - else -> throw IllegalArgumentException("typeToken should be an instance of Class, GenericArray, ParametrizedType or WildcardType, but actual type is $type ${type::class}") + else -> throw IllegalArgumentException("type should be an instance of Class, GenericArrayType, ParametrizedType or WildcardType, but actual argument $type has type ${type::class}") } @OptIn(ExperimentalSerializationApi::class) @@ -177,5 +196,5 @@ private fun Type.prettyClass(): Class<*> = when (val it = this) { is ParameterizedType -> it.rawType.prettyClass() is WildcardType -> it.upperBounds.first().prettyClass() is GenericArrayType -> it.genericComponentType.prettyClass() - else -> throw IllegalArgumentException("typeToken should be an instance of Class, GenericArray, ParametrizedType or WildcardType, but actual type is $it ${it::class}") + else -> throw IllegalArgumentException("type should be an instance of Class, GenericArrayType, ParametrizedType or WildcardType, but actual argument $it has type ${it::class}") } diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt index 95b458c9e..89e53a7aa 100644 --- a/formats/json-tests/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt +++ b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt @@ -101,6 +101,20 @@ class SerializersLookupTest : JsonTestBase() { assertSerializedWithType>("[1,2,3]", myList) } + @Test + fun testStarProjectionsAreProhibited() { + val expectedMessage = "Star projections in type arguments are not allowed" + assertFailsWithMessage(expectedMessage) { + serializer>() + } + assertFailsWithMessage(expectedMessage) { + serializer(typeOf>()) + } + assertFailsWithMessage(expectedMessage) { + serializerOrNull(typeOf>()) + } + } + @Test fun testNullableTypes() { val myList: List = listOf(1, null, 3) @@ -120,6 +134,12 @@ class SerializersLookupTest : JsonTestBase() { assertSerializedWithType("""{"first":"1","second":2,"third":{"boxed":42}}""", myTriple) } + @Test + fun testLookupDuration() = noLegacyJs { + assertNotNull(serializerOrNull(typeOf())) + assertSame(Duration.serializer(), serializer()) + } + @Test fun testCustomGeneric() = noLegacyJs { val intBox = Box(42) @@ -259,13 +279,6 @@ class SerializersLookupTest : JsonTestBase() { } } -// TODO uncomment when Kotlin 1.7.20 is released -// @Test -// fun testLookupDuration() = noLegacyJs { -// assertNotNull(serializerOrNull(typeOf())) -// assertSame(Duration.serializer(), serializer()) -// } - private inline fun assertSerializedWithType( expected: String, value: T,