diff options
author | Sergey Shanshin <sergey.shanshin@jetbrains.com> | 2022-09-07 12:47:47 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-07 12:47:47 +0300 |
commit | 2825e213ac584cb3fb8c4329b9782020be0bef7b (patch) | |
tree | c119b0b58b134051bccdf3e9bf3ac9c2a12cc694 | |
parent | 49dad8635d31d3105478807637b868c04c150f7e (diff) | |
download | kotlinx.serialization-2825e213ac584cb3fb8c4329b9782020be0bef7b.tar.gz |
Implemented serializers caching for lookup
10 files changed, 424 insertions, 53 deletions
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt new file mode 100644 index 00000000..ddfc5792 --- /dev/null +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.benchmarks.json + +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.* + +@Warmup(iterations = 7, time = 1) +@Measurement(iterations = 7, time = 1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +@Fork(2) +open class LookupOverheadBenchmark { + + @Serializable + class Holder(val a: String) + + @Serializable + class Generic<T>(val a: T) + + @Serializable + class DoubleGeneric<T1, T2>(val a: T1, val b: T2) + + @Serializable + class PentaGeneric<T1, T2, T3, T4, T5>(val a: T1, val b: T2, val c: T3, val d: T4, val e: T5) + + private val data = """{"a":""}""" + private val doubleData = """{"a":"","b":0}""" + private val pentaData = """{"a":"","b":0,"c":1,"d":true,"e":" "}""" + + @Serializable + object Object + + @Benchmark + fun dataReified() = Json.decodeFromString<Holder>(data) + + @Benchmark + fun dataPlain() = Json.decodeFromString(Holder.serializer(), data) + + @Benchmark + fun genericReified() = Json.decodeFromString<Generic<String>>(data) + + @Benchmark + fun genericPlain() = Json.decodeFromString(Generic.serializer(String.serializer()), data) + + @Benchmark + fun doubleGenericReified() = Json.decodeFromString<DoubleGeneric<String, Int>>(doubleData) + + @Benchmark + fun doubleGenericPlain() = Json.decodeFromString(DoubleGeneric.serializer(String.serializer(), Int.serializer()), doubleData) + + @Benchmark + fun pentaGenericReified() = Json.decodeFromString<PentaGeneric<String, Int, Long, Boolean, Char>>(pentaData) + + @Benchmark + fun pentaGenericPlain() = Json.decodeFromString(PentaGeneric.serializer(String.serializer(), Int.serializer(), Long.serializer(), Boolean.serializer(), Char.serializer()), pentaData) + + @Benchmark + fun objectReified() = Json.decodeFromString<Object>("{}") + + @Benchmark + fun objectPlain() = Json.decodeFromString(Object.serializer(), "{}") +} diff --git a/build.gradle b/build.gradle index 0d21932a..f363e1d2 100644 --- a/build.gradle +++ b/build.gradle @@ -173,7 +173,7 @@ subprojects { afterEvaluate { // Can be applied only when the project is evaluated animalsniffer { sourceSets = [sourceSets.main] - annotation = "kotlinx.serialization.json.internal.SuppressAnimalSniffer" + annotation = (name == "kotlinx-serialization-core")? "kotlinx.serialization.internal.SuppressAnimalSniffer" : "kotlinx.serialization.json.internal.SuppressAnimalSniffer" } dependencies { signature 'net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature' diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt index ff35a9f1..8063e1a8 100644 --- a/core/commonMain/src/kotlinx/serialization/Serializers.kt +++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt @@ -66,9 +66,8 @@ public fun SerializersModule.serializer(type: KType): KSerializer<Any?> = * Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable and is not registered in [this] module). */ @OptIn(ExperimentalSerializationApi::class) -public fun SerializersModule.serializerOrNull(type: KType): KSerializer<Any?>? { - return serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false) -} +public fun SerializersModule.serializerOrNull(type: KType): KSerializer<Any?>? = + serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false) @OptIn(ExperimentalSerializationApi::class) private fun SerializersModule.serializerByKTypeImpl( @@ -79,54 +78,47 @@ private fun SerializersModule.serializerByKTypeImpl( 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) -} -@OptIn(ExperimentalSerializationApi::class) -private fun SerializersModule.builtinSerializer( - typeArguments: List<KType>, - rootClass: KClass<Any>, - failOnMissingTypeArgSerializer: Boolean -): KSerializer<out Any>? { - val serializers = if (failOnMissingTypeArgSerializer) - typeArguments.map(::serializer) - else { - typeArguments.map { serializerOrNull(it) ?: return null } + val cachedSerializer = if (typeArguments.isEmpty()) { + findCachedSerializer(rootClass, isNullable) + } else { + val cachedResult = findParametrizedCachedSerializer(rootClass, typeArguments, isNullable) + if (failOnMissingTypeArgSerializer) { + cachedResult.getOrNull() + } else { + // return null if error occurred - serializer for parameter(s) was not found + cachedResult.getOrElse { 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] + 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(typeArguments, serializers) ?: getContextual( + rootClass, + serializers ) - 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) - } } + return contextualSerializer?.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 } /** @@ -179,6 +171,47 @@ 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( + types: List<KType>, + serializers: List<KSerializer<Any?>> +): KSerializer<out Any>? { + // builtin first because some standard parametrized interfaces (e.g. Map) must use builtin serializer but not polymorphic + return builtinParametrizedSerializer(types, serializers) ?: 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( + 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]) + 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(typeArguments[0].classifier 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?> diff --git a/core/commonMain/src/kotlinx/serialization/SerializersCache.kt b/core/commonMain/src/kotlinx/serialization/SerializersCache.kt new file mode 100644 index 00000000..b1481884 --- /dev/null +++ b/core/commonMain/src/kotlinx/serialization/SerializersCache.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization + +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.internal.cast +import kotlinx.serialization.internal.createCache +import kotlinx.serialization.internal.createParametrizedCache +import kotlinx.serialization.modules.EmptySerializersModule +import kotlin.native.concurrent.ThreadLocal +import kotlin.reflect.KClass +import kotlin.reflect.KType + + +/** + * Cache for non-null non-parametrized and non-contextual serializers. + */ +@ThreadLocal +private val SERIALIZERS_CACHE = createCache { it.serializerOrNull() } + +/** + * Cache for nullable non-parametrized and non-contextual serializers. + */ +@ThreadLocal +private val SERIALIZERS_CACHE_NULLABLE = createCache<Any?> { it.serializerOrNull()?.nullable?.cast() } + +/** + * Cache for non-null parametrized and non-contextual serializers. + */ +@ThreadLocal +private val PARAMETRIZED_SERIALIZERS_CACHE = createParametrizedCache { clazz, types -> + val serializers = EmptySerializersModule().serializersForParameters(types, true)!! + clazz.parametrizedSerializerOrNull(types, serializers) +} + +/** + * Cache for nullable parametrized and non-contextual serializers. + */ +@ThreadLocal +private val PARAMETRIZED_SERIALIZERS_CACHE_NULLABLE = createParametrizedCache<Any?> { clazz, types -> + val serializers = EmptySerializersModule().serializersForParameters(types, true)!! + clazz.parametrizedSerializerOrNull(types, serializers)?.nullable?.cast() +} + +/** + * Find cacheable serializer in the cache. + * If serializer is cacheable but missed in cache - it will be created, placed into the cache and returned. + */ +internal fun findCachedSerializer(clazz: KClass<Any>, isNullable: Boolean): KSerializer<Any?>? { + return if (!isNullable) { + SERIALIZERS_CACHE.get(clazz)?.cast() + } else { + SERIALIZERS_CACHE_NULLABLE.get(clazz) + } +} + +/** + * Find cacheable parametrized serializer in the cache. + * If serializer is cacheable but missed in cache - it will be created, placed into the cache and returned. + */ +internal fun findParametrizedCachedSerializer( + clazz: KClass<Any>, + types: List<KType>, + isNullable: Boolean +): Result<KSerializer<Any?>?> { + return if (!isNullable) { + @Suppress("UNCHECKED_CAST") + PARAMETRIZED_SERIALIZERS_CACHE.get(clazz, types) as Result<KSerializer<Any?>?> + } else { + PARAMETRIZED_SERIALIZERS_CACHE_NULLABLE.get(clazz, types) + } +} diff --git a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt index fde2c36a..9c4bad7c 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt @@ -130,6 +130,18 @@ internal expect fun BooleanArray.getChecked(index: Int): Boolean internal expect fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? +/** + * Create serializers cache for non-parametrized and non-contextual serializers. + * The activity and type of cache is determined for a specific platform and a specific environment. + */ +internal expect fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> + +/** + * Create serializers cache for parametrized and non-contextual serializers. Parameters also non-contextual. + * The activity and type of cache is determined for a specific platform and a specific environment. + */ +internal expect fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> + internal expect fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> /** @@ -145,3 +157,24 @@ internal expect fun Any.isInstanceOf(kclass: KClass<*>): Boolean internal inline fun <T, K> Iterable<T>.elementsHashCodeBy(selector: (T) -> K): Int { return fold(1) { hash, element -> 31 * hash + selector(element).hashCode() } } + +/** + * Cache class for non-parametrized and non-contextual serializers. + */ +internal interface SerializerCache<T> { + /** + * Returns cached serializer or `null` if serializer not found. + */ + fun get(key: KClass<Any>): KSerializer<T>? +} + +/** + * Cache class for parametrized and non-contextual serializers. + */ +internal interface ParametrizedSerializerCache<T> { + /** + * Returns successful result with cached serializer or `null` if root serializer not found. + * If no serializer was found for the parameters, then result contains an exception. + */ + fun get(key: KClass<Any>, types: List<KType> = emptyList()): Result<KSerializer<T>?> +} diff --git a/core/jsMain/src/kotlinx/serialization/internal/Platform.kt b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt index 25c48146..a9534dda 100644 --- a/core/jsMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt @@ -20,6 +20,22 @@ internal actual fun BooleanArray.getChecked(index: Int): Boolean { internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? = this.constructSerializerForGivenTypeArgs() ?: this.js.asDynamic().Companion?.serializer() as? KSerializer<T> +internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> { + return object: SerializerCache<T> { + override fun get(key: KClass<Any>): KSerializer<T>? { + return factory(key) + } + } +} + +internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> { + return object: ParametrizedSerializerCache<T> { + override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> { + return kotlin.runCatching { factory(key, types) } + } + } +} + internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> = toTypedArray() internal actual fun Any.isInstanceOf(kclass: KClass<*>): Boolean = kclass.isInstance(this) diff --git a/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt b/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt new file mode 100644 index 00000000..39cec936 --- /dev/null +++ b/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.internal + +import kotlinx.serialization.KSerializer +import java.util.concurrent.ConcurrentHashMap +import kotlin.reflect.KClass +import kotlin.reflect.KType + +/* + * By default, we use ClassValue-based caches to avoid classloader leaks, + * but ClassValue is not available on Android, thus we attempt to check it dynamically + * and fallback to ConcurrentHashMap-based cache. + */ +private val useClassValue = runCatching { + Class.forName("java.lang.ClassValue") +}.map { true }.getOrDefault(false) + +/** + * Creates a **strongly referenced** cache of values associated with [Class]. + * Serializers are computed using provided [factory] function. + * + * `null` values are not supported, though there aren't any technical limitations. + */ +internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> { + return if (useClassValue) ClassValueCache(factory) else ConcurrentHashMapCache(factory) +} + +/** + * Creates a **strongly referenced** cache of values associated with [Class]. + * Serializers are computed using provided [factory] function. + * + * `null` values are not supported, though there aren't any technical limitations. + */ +internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> { + return if (useClassValue) ClassValueParametrizedCache(factory) else ConcurrentHashMapParametrizedCache(factory) +} + +@SuppressAnimalSniffer +private class ClassValueCache<T>(private val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> { + private val classValue = initClassValue() + + private fun initClassValue() = object : ClassValue<CacheEntry<T>>() { + /* + * Since during the computing of the value for the `ClassValue` entry, we do not know whether a nullable + * serializer is needed, so we may need to differentiate nullable/non-null caches by a level higher + */ + override fun computeValue(type: Class<*>): CacheEntry<T> { + return CacheEntry(compute(type.kotlin)) + } + } + + override fun get(key: KClass<Any>): KSerializer<T>? = classValue[key.java].serializer +} + +@SuppressAnimalSniffer +private class ClassValueParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) : ParametrizedSerializerCache<T> { + private val classValue = initClassValue() + + private fun initClassValue() = object : ClassValue<ParametrizedCacheEntry<T>>() { + /* + * Since during the computing of the value for the `ClassValue` entry, we do not know whether a nullable + * serializer is needed, so we may need to differentiate nullable/non-null caches by a level higher + */ + override fun computeValue(type: Class<*>): ParametrizedCacheEntry<T> { + return ParametrizedCacheEntry() + } + } + + override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> = + classValue[key.java].computeIfAbsent(types) { compute(key, types) } +} + +/** + * We no longer support Java 6, so the only place we use this cache is Android, where there + * are no classloader leaks issue, thus we can safely use strong references and do not bother + * with WeakReference wrapping. + */ +private class ConcurrentHashMapCache<T>(private val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> { + private val cache = ConcurrentHashMap<Class<*>, CacheEntry<T>>() + + override fun get(key: KClass<Any>): KSerializer<T>? { + return cache.getOrPut(key.java) { + CacheEntry(compute(key)) + }.serializer + } +} + + + +private class ConcurrentHashMapParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) : ParametrizedSerializerCache<T> { + private val cache = ConcurrentHashMap<Class<*>, ParametrizedCacheEntry<T>>() + + override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> { + return cache.getOrPut(key.java) { ParametrizedCacheEntry() } + .computeIfAbsent(types) { compute(key, types) } + } +} + +private class CacheEntry<T>(@JvmField val serializer: KSerializer<T>?) + +private class ParametrizedCacheEntry<T> { + private val serializers: ConcurrentHashMap<List<KType>, Result<KSerializer<T>?>> = ConcurrentHashMap() + inline fun computeIfAbsent(types: List<KType>, producer: () -> KSerializer<T>?): Result<KSerializer<T>?> { + return serializers.getOrPut(types) { + kotlin.runCatching { producer() } + } + } +} + diff --git a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt index 7c16650b..1feb842e 100644 --- a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt @@ -61,7 +61,11 @@ internal fun <T: Any> Class<T>.constructSerializerForGivenTypeArgs(vararg args: } if (fromNamedCompanion != null) return fromNamedCompanion // Check for polymorphic - return polymorphicSerializer() + return if (isPolymorphicSerializer()) { + PolymorphicSerializer(this.kotlin) + } else { + null + } } private fun <T: Any> Class<T>.isNotAnnotated(): Boolean { @@ -72,19 +76,19 @@ private fun <T: Any> Class<T>.isNotAnnotated(): Boolean { getAnnotation(Polymorphic::class.java) == null } -private fun <T: Any> Class<T>.polymorphicSerializer(): KSerializer<T>? { +private fun <T: Any> Class<T>.isPolymorphicSerializer(): Boolean { /* * Last resort: check for @Polymorphic or Serializable(with = PolymorphicSerializer::class) * annotations. */ if (getAnnotation(Polymorphic::class.java) != null) { - return PolymorphicSerializer(this.kotlin) + return true } val serializable = getAnnotation(Serializable::class.java) if (serializable != null && serializable.with == PolymorphicSerializer::class) { - return PolymorphicSerializer(this.kotlin) + return true } - return null + return false } private fun <T: Any> Class<T>.interfaceSerializer(): KSerializer<T>? { @@ -125,9 +129,9 @@ private fun Class<*>.companionOrNull() = } @Suppress("UNCHECKED_CAST") -private fun <T : Any> Class<T>.createEnumSerializer(): KSerializer<T>? { +private fun <T : Any> Class<T>.createEnumSerializer(): KSerializer<T> { val constants = enumConstants - return EnumSerializer(canonicalName, constants as Array<out Enum<*>>) as? KSerializer<T> + return EnumSerializer(canonicalName, constants as Array<out Enum<*>>) as KSerializer<T> } private fun <T : Any> Class<T>.findObjectSerializer(): KSerializer<T>? { diff --git a/core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt b/core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt new file mode 100644 index 00000000..7b3cc310 --- /dev/null +++ b/core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.internal + +/** + * Suppresses Animal Sniffer plugin errors for certain classes. + * Such classes are not available in Android API, but used only for JVM. + */ +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS) +internal annotation class SuppressAnimalSniffer diff --git a/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt b/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt index e24c1820..1c0d5ab3 100644 --- a/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt @@ -45,6 +45,23 @@ internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vara internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? = this.constructSerializerForGivenTypeArgs() + +internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> { + return object: SerializerCache<T> { + override fun get(key: KClass<Any>): KSerializer<T>? { + return factory(key) + } + } +} + +internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> { + return object: ParametrizedSerializerCache<T> { + override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> { + return kotlin.runCatching { factory(key, types) } + } + } +} + internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> { val result = arrayOfAnyNulls<E>(size) var index = 0 |