Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove experimentality from serializer(java.lang.Type) function family #2069

Merged
merged 3 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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