summaryrefslogtreecommitdiff
path: root/core/commonMain/src/kotlinx/serialization/Serializers.kt
diff options
context:
space:
mode:
Diffstat (limited to 'core/commonMain/src/kotlinx/serialization/Serializers.kt')
-rw-r--r--core/commonMain/src/kotlinx/serialization/Serializers.kt322
1 files changed, 259 insertions, 63 deletions
diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt
index a5211877..2489be27 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<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()
@@ -34,41 +56,129 @@ public inline fun <reified T> SerializersModule.serializer(): KSerializer<T> {
/**
* 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)
+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).
+ * Retrieves serializer for the given [kClass].
+ * This method uses platform-specific reflection available.
+ *
+ * If [kClass] is a parametrized type then it is necessary to pass serializers for generic parameters in the [typeArgumentsSerializers].
+ * The nullability of returned serializer is specified using the [isNullable].
+ *
+ * Note that it is impossible to create an array serializer with this method,
+ * as array serializer needs additional information: type token for an element type.
+ * To create array serializer, use overload with [KType] or [ArraySerializer] directly.
+ *
+ * Caching on JVM platform is disabled for this function, so it may work slower than an overload with [KType].
+ *
+ * @throws SerializationException if serializer cannot be created (provided [kClass] or its type argument is not serializable)
+ * @throws SerializationException if [kClass] is a `kotlin.Array`
+ * @throws SerializationException if size of [typeArgumentsSerializers] does not match the expected generic parameters count
*/
-@OptIn(ExperimentalSerializationApi::class)
-public fun serializerOrNull(type: KType): KSerializer<Any?>? = EmptySerializersModule.serializerOrNull(type)
+@ExperimentalSerializationApi
+public fun serializer(
+ kClass: KClass<*>,
+ typeArgumentsSerializers: List<KSerializer<*>>,
+ isNullable: Boolean
+): KSerializer<Any?> = EmptySerializersModule().serializer(kClass, typeArgumentsSerializers, isNullable)
/**
- * 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.
+ * 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.
+ *
+ * @return [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
+ */
+public fun serializerOrNull(type: KType): KSerializer<Any?>? = EmptySerializersModule().serializerOrNull(type)
+
+/**
+ * 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 serializer for the given [kClass] and,
+ * if [kClass] is not serializable, fallbacks to [contextual][SerializersModule.getContextual] lookup.
+ * This method uses platform-specific reflection available.
+ *
+ * If [kClass] is a parametrized type then it is necessary to pass serializers for generic parameters in the [typeArgumentsSerializers].
+ * The nullability of returned serializer is specified using the [isNullable].
+ *
+ * Note that it is impossible to create an array serializer with this method,
+ * as array serializer needs additional information: type token for an element type.
+ * To create array serializer, use overload with [KType] or [ArraySerializer] directly.
+ *
+ * Caching on JVM platform is disabled for this function, so it may work slower than an overload with [KType].
+ *
+ * @throws SerializationException if serializer cannot be created (provided [kClass] or its type argument is not serializable and is not registered in [this] module)
+ * @throws SerializationException if [kClass] is a `kotlin.Array`
+ * @throws SerializationException if size of [typeArgumentsSerializers] does not match the expected generic parameters count
*/
-@OptIn(ExperimentalSerializationApi::class)
-public fun SerializersModule.serializerOrNull(type: KType): KSerializer<Any?>? {
- return serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false)
-}
+@ExperimentalSerializationApi
+public fun SerializersModule.serializer(
+ kClass: KClass<*>,
+ typeArgumentsSerializers: List<KSerializer<*>>,
+ isNullable: Boolean
+): KSerializer<Any?> =
+ serializerByKClassImpl(kClass as KClass<Any>, typeArgumentsSerializers as List<KSerializer<Any?>>, isNullable)
+ ?: kClass.platformSpecificSerializerNotRegistered()
+
+/**
+ * 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.
+ *
+ * @return [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
+ */
+public fun SerializersModule.serializerOrNull(type: KType): KSerializer<Any?>? =
+ serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false)
@OptIn(ExperimentalSerializationApi::class)
private fun SerializersModule.serializerByKTypeImpl(
@@ -77,53 +187,67 @@ private fun SerializersModule.serializerByKTypeImpl(
): KSerializer<Any?>? {
val rootClass = type.kclass()
val isNullable = type.isMarkedNullable
- val typeArguments = type.arguments
- .map { requireNotNull(it.type) { "Star projections in type arguments are not allowed, but had $type" } }
- val result: KSerializer<Any>? = when {
- typeArguments.isEmpty() -> rootClass.serializerOrNull() ?: getContextual(rootClass)
- else -> builtinSerializer(typeArguments, rootClass, failOnMissingTypeArgSerializer)
- }?.cast()
- return result?.nullable(isNullable)
+ val typeArguments = type.arguments.map(KTypeProjection::typeOrThrow)
+
+ val cachedSerializer = if (typeArguments.isEmpty()) {
+ findCachedSerializer(rootClass, isNullable)
+ } else {
+ findParametrizedCachedSerializer(rootClass, typeArguments, isNullable).getOrNull()
+ }
+ cachedSerializer?.let { return it }
+
+ // slow path to find contextual serializers in serializers module
+ val contextualSerializer: KSerializer<out Any?>? = if (typeArguments.isEmpty()) {
+ getContextual(rootClass)
+ } else {
+ val serializers = serializersForParameters(typeArguments, failOnMissingTypeArgSerializer) ?: return null
+ // first, we look among the built-in serializers, because the parameter could be contextual
+ rootClass.parametrizedSerializerOrNull(serializers) { typeArguments[0].classifier }
+ ?: getContextual(
+ rootClass,
+ serializers
+ )
+ }
+ return contextualSerializer?.cast<Any>()?.nullable(isNullable)
}
@OptIn(ExperimentalSerializationApi::class)
-private fun SerializersModule.builtinSerializer(
- typeArguments: List<KType>,
+private fun SerializersModule.serializerByKClassImpl(
rootClass: KClass<Any>,
- failOnMissingTypeArgSerializer: Boolean
-): KSerializer<out Any>? {
- val serializers = if (failOnMissingTypeArgSerializer)
- typeArguments.map(::serializer)
- else {
- typeArguments.map { serializerOrNull(it) ?: return null }
- }
- // Array is not supported, see KT-32839
- return when (rootClass) {
- Collection::class, List::class, MutableList::class, ArrayList::class -> ArrayListSerializer(serializers[0])
- HashSet::class -> HashSetSerializer(serializers[0])
- Set::class, MutableSet::class, LinkedHashSet::class -> LinkedHashSetSerializer(serializers[0])
- HashMap::class -> HashMapSerializer(serializers[0], serializers[1])
- Map::class, MutableMap::class, LinkedHashMap::class -> LinkedHashMapSerializer(
- serializers[0],
- serializers[1]
- )
- Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1])
- Pair::class -> PairSerializer(serializers[0], serializers[1])
- Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2])
- else -> {
- if (isReferenceArray(rootClass)) {
- return ArraySerializer<Any, Any?>(typeArguments[0].classifier as KClass<Any>, serializers[0]).cast()
- }
- val args = serializers.toTypedArray()
- rootClass.constructSerializerForGivenTypeArgs(*args)
- ?: reflectiveOrContextual(rootClass, serializers)
+ typeArgumentsSerializers: List<KSerializer<Any?>>,
+ isNullable: Boolean
+): KSerializer<Any?>? {
+ val serializer = if (typeArgumentsSerializers.isEmpty()) {
+ rootClass.serializerOrNull() ?: getContextual(rootClass)
+ } else {
+ try {
+ rootClass.parametrizedSerializerOrNull(typeArgumentsSerializers) {
+ throw SerializationException("It is not possible to retrieve an array serializer using KClass alone, use KType instead or ArraySerializer factory")
+ } ?: getContextual(
+ rootClass,
+ typeArgumentsSerializers
+ )
+ } catch (e: IndexOutOfBoundsException) {
+ throw SerializationException("Unable to retrieve a serializer, the number of passed type serializers differs from the actual number of generic parameters", e)
}
}
+
+ return serializer?.cast<Any>()?.nullable(isNullable)
}
-@OptIn(ExperimentalSerializationApi::class)
-internal fun <T : Any> SerializersModule.reflectiveOrContextual(kClass: KClass<T>, typeArgumentsSerializers: List<KSerializer<Any?>>): KSerializer<T>? {
- return kClass.serializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers)
+/**
+ * Returns null only if `failOnMissingTypeArgSerializer == false` and at least one parameter serializer not found.
+ */
+internal fun SerializersModule.serializersForParameters(
+ typeArguments: List<KType>,
+ failOnMissingTypeArgSerializer: Boolean
+): List<KSerializer<Any?>>? {
+ val serializers = if (failOnMissingTypeArgSerializer) {
+ typeArguments.map { serializer(it) }
+ } else {
+ typeArguments.map { serializerOrNull(it) ?: return null }
+ }
+ return serializers
}
/**
@@ -176,7 +300,79 @@ public fun <T : Any> KClass<T>.serializer(): KSerializer<T> = serializerOrNull()
public fun <T : Any> KClass<T>.serializerOrNull(): KSerializer<T>? =
compiledSerializerImpl() ?: builtinSerializerOrNull()
+internal fun KClass<Any>.parametrizedSerializerOrNull(
+ serializers: List<KSerializer<Any?>>,
+ elementClassifierIfArray: () -> KClassifier?
+): KSerializer<out Any>? {
+ // builtin first because some standard parametrized interfaces (e.g. Map) must use builtin serializer but not polymorphic
+ return builtinParametrizedSerializer(serializers, elementClassifierIfArray) ?: compiledParametrizedSerializer(serializers)
+}
+
+
+private fun KClass<Any>.compiledParametrizedSerializer(serializers: List<KSerializer<Any?>>): KSerializer<out Any>? {
+ return constructSerializerForGivenTypeArgs(*serializers.toTypedArray())
+}
+
+@OptIn(ExperimentalSerializationApi::class)
+private fun KClass<Any>.builtinParametrizedSerializer(
+ serializers: List<KSerializer<Any?>>,
+ elementClassifierIfArray: () -> KClassifier?
+): KSerializer<out Any>? {
+ return when (this) {
+ Collection::class, List::class, MutableList::class, ArrayList::class -> ArrayListSerializer(serializers[0])
+ HashSet::class -> HashSetSerializer(serializers[0])
+ Set::class, MutableSet::class, LinkedHashSet::class -> LinkedHashSetSerializer(serializers[0])
+ HashMap::class -> HashMapSerializer(serializers[0], serializers[1])
+ Map::class, MutableMap::class, LinkedHashMap::class -> LinkedHashMapSerializer(
+ serializers[0],
+ serializers[1]
+ )
+
+ Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1])
+ Pair::class -> PairSerializer(serializers[0], serializers[1])
+ Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2])
+ else -> {
+ if (isReferenceArray(this)) {
+ ArraySerializer(elementClassifierIfArray() as KClass<Any>, serializers[0])
+ } else {
+ null
+ }
+ }
+ }
+}
+
private fun <T : Any> KSerializer<T>.nullable(shouldBeNullable: Boolean): KSerializer<T?> {
if (shouldBeNullable) return nullable
return this as KSerializer<T?>
}
+
+
+/**
+ * Overloads of [noCompiledSerializer] should never be called directly.
+ * Instead, compiler inserts calls to them when intrinsifying [serializer] function.
+ *
+ * If no serializer has been found in compile time, call to [noCompiledSerializer] inserted instead.
+ */
+@Suppress("unused")
+@PublishedApi
+internal fun noCompiledSerializer(forClass: String): KSerializer<*> =
+ throw SerializationException(notRegisteredMessage(forClass))
+
+// Used when compiler intrinsic is inserted
+@OptIn(ExperimentalSerializationApi::class)
+@Suppress("unused")
+@PublishedApi
+internal fun noCompiledSerializer(module: SerializersModule, kClass: KClass<*>): KSerializer<*> {
+ return module.getContextual(kClass) ?: kClass.serializerNotRegistered()
+}
+
+@OptIn(ExperimentalSerializationApi::class)
+@Suppress("unused")
+@PublishedApi
+internal fun noCompiledSerializer(
+ module: SerializersModule,
+ kClass: KClass<*>,
+ argSerializers: Array<KSerializer<*>>
+): KSerializer<*> {
+ return module.getContextual(kClass, argSerializers.asList()) ?: kClass.serializerNotRegistered()
+}