Skip to content

Commit

Permalink
Remove experimentality from serializer(java.lang.Type) function family (
Browse files Browse the repository at this point in the history
Kotlin#2069)

As it is needed for third-part converter libraries.

Revamp docs for all overloads of serializer().
  • Loading branch information
sandwwraith authored and fred01 committed Nov 24, 2022
1 parent b581abe commit 0ae611d
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 58 deletions.
97 changes: 77 additions & 20 deletions core/commonMain/src/kotlinx/serialization/Serializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,54 +18,112 @@ 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<List<String?>>()` 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 <reified T> serializer(): KSerializer<T> {
return serializer(typeOf<T>()).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<List<String?>>()` 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 <reified T> SerializersModule.serializer(): KSerializer<T> {
return serializer(typeOf<T>()).cast()
}

/**
* 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<typeOf<List<String?>>>()` 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<Any?> = 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<typeOf<List<String?>>>()` 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<Any?>? = 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<typeOf<List<String?>>>()` 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<Any?> =
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<typeOf<List<String?>>>()` 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<Any?>? =
serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false)

Expand Down Expand Up @@ -189,7 +247,6 @@ private fun KClass<Any>.builtinParametrizedSerializer(
typeArguments: List<KType>,
serializers: List<KSerializer<Any?>>,
): KSerializer<out Any>? {
// 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])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down
81 changes: 50 additions & 31 deletions core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>()` 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<T>()` 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<Any> = 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<T>()` 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<T>()` 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<Any>? = 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<T>()` 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<T>()` 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<Any> =
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<T>()` 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<T>()` 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<Any>? =
serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = false)

@OptIn(ExperimentalSerializationApi::class)
private fun SerializersModule.serializerByJavaTypeImpl(
type: Type,
failOnMissingTypeArgSerializer: Boolean = true
Expand Down Expand Up @@ -116,15 +137,13 @@ private fun SerializersModule.serializerByJavaTypeImpl(
) as KSerializer<Any>

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<Any?> }
reflectiveOrContextual(rootClass as Class<Any>, 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)
Expand Down Expand Up @@ -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}")
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ class SerializersLookupTest : JsonTestBase() {
assertSerializedWithType<ArrayList<in Int>>("[1,2,3]", myList)
}

@Test
fun testStarProjectionsAreProhibited() {
val expectedMessage = "Star projections in type arguments are not allowed"
assertFailsWithMessage<IllegalArgumentException>(expectedMessage) {
serializer<Box<*>>()
}
assertFailsWithMessage<IllegalArgumentException>(expectedMessage) {
serializer(typeOf<Box<*>>())
}
assertFailsWithMessage<IllegalArgumentException>(expectedMessage) {
serializerOrNull(typeOf<Box<*>>())
}
}

@Test
fun testNullableTypes() {
val myList: List<Int?> = listOf(1, null, 3)
Expand All @@ -120,6 +134,12 @@ class SerializersLookupTest : JsonTestBase() {
assertSerializedWithType("""{"first":"1","second":2,"third":{"boxed":42}}""", myTriple)
}

@Test
fun testLookupDuration() = noLegacyJs {
assertNotNull(serializerOrNull(typeOf<Duration>()))
assertSame(Duration.serializer(), serializer<Duration>())
}

@Test
fun testCustomGeneric() = noLegacyJs {
val intBox = Box(42)
Expand Down Expand Up @@ -259,13 +279,6 @@ class SerializersLookupTest : JsonTestBase() {
}
}

// TODO uncomment when Kotlin 1.7.20 is released
// @Test
// fun testLookupDuration() = noLegacyJs {
// assertNotNull(serializerOrNull(typeOf<Duration>()))
// assertSame(Duration.serializer(), serializer<Duration>())
// }

private inline fun <reified T> assertSerializedWithType(
expected: String,
value: T,
Expand Down

0 comments on commit 0ae611d

Please sign in to comment.