diff options
Diffstat (limited to 'core')
82 files changed, 3140 insertions, 639 deletions
diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index ede49f69..720e5847 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -26,6 +26,7 @@ public abstract interface annotation class kotlinx/serialization/EncodeDefault : public final class kotlinx/serialization/EncodeDefault$Mode : java/lang/Enum { public static final field ALWAYS Lkotlinx/serialization/EncodeDefault$Mode; public static final field NEVER Lkotlinx/serialization/EncodeDefault$Mode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lkotlinx/serialization/EncodeDefault$Mode; public static fun values ()[Lkotlinx/serialization/EncodeDefault$Mode; } @@ -43,8 +44,18 @@ public abstract interface class kotlinx/serialization/KSerializer : kotlinx/seri public abstract fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; } +public abstract interface annotation class kotlinx/serialization/KeepGeneratedSerializer : java/lang/annotation/Annotation { +} + +public abstract interface annotation class kotlinx/serialization/MetaSerializable : java/lang/annotation/Annotation { +} + public final class kotlinx/serialization/MissingFieldException : kotlinx/serialization/SerializationException { public fun <init> (Ljava/lang/String;)V + public fun <init> (Ljava/lang/String;Ljava/lang/String;)V + public fun <init> (Ljava/util/List;Ljava/lang/String;)V + public fun <init> (Ljava/util/List;Ljava/lang/String;Ljava/lang/Throwable;)V + public final fun getMissingFields ()Ljava/util/List; } public abstract interface annotation class kotlinx/serialization/Polymorphic : java/lang/annotation/Annotation { @@ -112,10 +123,15 @@ public abstract interface annotation class kotlinx/serialization/Serializer : ja } public final class kotlinx/serialization/SerializersKt { + public static final fun noCompiledSerializer (Ljava/lang/String;)Lkotlinx/serialization/KSerializer; + public static final fun noCompiledSerializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer; + public static final fun noCompiledSerializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer; + public static final fun serializer (Lkotlin/reflect/KClass;Ljava/util/List;Z)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlinx/serialization/modules/SerializersModule;Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer; + public static final fun serializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;Ljava/util/List;Z)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer; public static final fun serializerOrNull (Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer; public static final fun serializerOrNull (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer; @@ -156,10 +172,15 @@ public final class kotlinx/serialization/builtins/BuiltinSerializersKt { public static final fun LongArraySerializer ()Lkotlinx/serialization/KSerializer; public static final fun MapEntrySerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; public static final fun MapSerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; + public static final fun NothingSerializer ()Lkotlinx/serialization/KSerializer; public static final fun PairSerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; public static final fun SetSerializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; public static final fun ShortArraySerializer ()Lkotlinx/serialization/KSerializer; public static final fun TripleSerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; + public static final fun UByteArraySerializer ()Lkotlinx/serialization/KSerializer; + public static final fun UIntArraySerializer ()Lkotlinx/serialization/KSerializer; + public static final fun ULongArraySerializer ()Lkotlinx/serialization/KSerializer; + public static final fun UShortArraySerializer ()Lkotlinx/serialization/KSerializer; public static final fun getNullable (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/UByte$Companion;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/UInt$Companion;)Lkotlinx/serialization/KSerializer; @@ -175,6 +196,7 @@ public final class kotlinx/serialization/builtins/BuiltinSerializersKt { public static final fun serializer (Lkotlin/jvm/internal/LongCompanionObject;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/jvm/internal/ShortCompanionObject;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/jvm/internal/StringCompanionObject;)Lkotlinx/serialization/KSerializer; + public static final fun serializer (Lkotlin/time/Duration$Companion;)Lkotlinx/serialization/KSerializer; } public final class kotlinx/serialization/builtins/LongAsStringSerializer : kotlinx/serialization/KSerializer { @@ -398,6 +420,10 @@ public abstract class kotlinx/serialization/encoding/AbstractEncoder : kotlinx/s public fun shouldEncodeElementDefault (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Z } +public abstract interface class kotlinx/serialization/encoding/ChunkedDecoder { + public abstract fun decodeStringChunked (Lkotlin/jvm/functions/Function1;)V +} + public abstract interface class kotlinx/serialization/encoding/CompositeDecoder { public static final field Companion Lkotlinx/serialization/encoding/CompositeDecoder$Companion; public static final field DECODE_DONE I @@ -662,6 +688,15 @@ public final class kotlinx/serialization/internal/DoubleSerializer : kotlinx/ser public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V } +public final class kotlinx/serialization/internal/DurationSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Lkotlinx/serialization/internal/DurationSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize-5sfh64U (Lkotlinx/serialization/encoding/Decoder;)J + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize-HG0u8IE (Lkotlinx/serialization/encoding/Encoder;J)V +} + public final class kotlinx/serialization/internal/ElementMarker { public fun <init> (Lkotlinx/serialization/descriptors/SerialDescriptor;Lkotlin/jvm/functions/Function2;)V public final fun mark (I)V @@ -750,6 +785,10 @@ public final class kotlinx/serialization/internal/InlineClassDescriptor : kotlin public fun isInline ()Z } +public final class kotlinx/serialization/internal/InlineClassDescriptorKt { + public static final fun InlinePrimitiveDescriptor (Ljava/lang/String;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/descriptors/SerialDescriptor; +} + public final class kotlinx/serialization/internal/IntArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder { public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object; } @@ -856,6 +895,9 @@ public abstract class kotlinx/serialization/internal/MapLikeSerializer : kotlinx public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V } +public abstract interface annotation class kotlinx/serialization/internal/NamedCompanion : java/lang/annotation/Annotation { +} + public abstract class kotlinx/serialization/internal/NamedValueDecoder : kotlinx/serialization/internal/TaggedDecoder { public fun <init> ()V protected fun composeName (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; @@ -874,6 +916,15 @@ public abstract class kotlinx/serialization/internal/NamedValueEncoder : kotlinx protected final fun nested (Ljava/lang/String;)Ljava/lang/String; } +public final class kotlinx/serialization/internal/NothingSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Lkotlinx/serialization/internal/NothingSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Void; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Void;)V +} + public final class kotlinx/serialization/internal/NullableSerializer : kotlinx/serialization/KSerializer { public fun <init> (Lkotlinx/serialization/KSerializer;)V public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; @@ -1025,7 +1076,7 @@ public abstract class kotlinx/serialization/internal/TaggedDecoder : kotlinx/ser public final fun decodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;)I public final fun decodeFloat ()F public final fun decodeFloatElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)F - public final fun decodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Decoder; + public fun decodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Decoder; public final fun decodeInlineElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/Decoder; public final fun decodeInt ()I public final fun decodeIntElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)I @@ -1081,13 +1132,13 @@ public abstract class kotlinx/serialization/internal/TaggedEncoder : kotlinx/ser public final fun encodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;I)V public final fun encodeFloat (F)V public final fun encodeFloatElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IF)V - public final fun encodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Encoder; + public fun encodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Encoder; public final fun encodeInlineElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/Encoder; public final fun encodeInt (I)V public final fun encodeIntElement (Lkotlinx/serialization/descriptors/SerialDescriptor;II)V public final fun encodeLong (J)V public final fun encodeLongElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IJ)V - public final fun encodeNotNullMark ()V + public fun encodeNotNullMark ()V public fun encodeNull ()V public fun encodeNullableSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V public fun encodeNullableSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V @@ -1106,6 +1157,7 @@ public abstract class kotlinx/serialization/internal/TaggedEncoder : kotlinx/ser protected fun encodeTaggedInline (Ljava/lang/Object;Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Encoder; protected fun encodeTaggedInt (Ljava/lang/Object;I)V protected fun encodeTaggedLong (Ljava/lang/Object;J)V + protected fun encodeTaggedNonNullMark (Ljava/lang/Object;)V protected fun encodeTaggedNull (Ljava/lang/Object;)V protected fun encodeTaggedShort (Ljava/lang/Object;S)V protected fun encodeTaggedString (Ljava/lang/Object;Ljava/lang/String;)V @@ -1130,6 +1182,20 @@ public final class kotlinx/serialization/internal/TripleSerializer : kotlinx/ser public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlin/Triple;)V } +public final class kotlinx/serialization/internal/UByteArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder { + public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object; +} + +public final class kotlinx/serialization/internal/UByteArraySerializer : kotlinx/serialization/internal/PrimitiveArraySerializer, kotlinx/serialization/KSerializer { + public static final field INSTANCE Lkotlinx/serialization/internal/UByteArraySerializer; + public synthetic fun collectionSize (Ljava/lang/Object;)I + public synthetic fun empty ()Ljava/lang/Object; + public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V + public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILkotlinx/serialization/internal/PrimitiveArrayBuilder;Z)V + public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun writeContent (Lkotlinx/serialization/encoding/CompositeEncoder;Ljava/lang/Object;I)V +} + public final class kotlinx/serialization/internal/UByteSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lkotlinx/serialization/internal/UByteSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; @@ -1139,6 +1205,20 @@ public final class kotlinx/serialization/internal/UByteSerializer : kotlinx/seri public fun serialize-EK-6454 (Lkotlinx/serialization/encoding/Encoder;B)V } +public final class kotlinx/serialization/internal/UIntArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder { + public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object; +} + +public final class kotlinx/serialization/internal/UIntArraySerializer : kotlinx/serialization/internal/PrimitiveArraySerializer, kotlinx/serialization/KSerializer { + public static final field INSTANCE Lkotlinx/serialization/internal/UIntArraySerializer; + public synthetic fun collectionSize (Ljava/lang/Object;)I + public synthetic fun empty ()Ljava/lang/Object; + public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V + public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILkotlinx/serialization/internal/PrimitiveArrayBuilder;Z)V + public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun writeContent (Lkotlinx/serialization/encoding/CompositeEncoder;Ljava/lang/Object;I)V +} + public final class kotlinx/serialization/internal/UIntSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lkotlinx/serialization/internal/UIntSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; @@ -1148,6 +1228,20 @@ public final class kotlinx/serialization/internal/UIntSerializer : kotlinx/seria public fun serialize-Qn1smSk (Lkotlinx/serialization/encoding/Encoder;I)V } +public final class kotlinx/serialization/internal/ULongArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder { + public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object; +} + +public final class kotlinx/serialization/internal/ULongArraySerializer : kotlinx/serialization/internal/PrimitiveArraySerializer, kotlinx/serialization/KSerializer { + public static final field INSTANCE Lkotlinx/serialization/internal/ULongArraySerializer; + public synthetic fun collectionSize (Ljava/lang/Object;)I + public synthetic fun empty ()Ljava/lang/Object; + public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V + public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILkotlinx/serialization/internal/PrimitiveArrayBuilder;Z)V + public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun writeContent (Lkotlinx/serialization/encoding/CompositeEncoder;Ljava/lang/Object;I)V +} + public final class kotlinx/serialization/internal/ULongSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lkotlinx/serialization/internal/ULongSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; @@ -1157,6 +1251,20 @@ public final class kotlinx/serialization/internal/ULongSerializer : kotlinx/seri public fun serialize-2TYgG_w (Lkotlinx/serialization/encoding/Encoder;J)V } +public final class kotlinx/serialization/internal/UShortArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder { + public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object; +} + +public final class kotlinx/serialization/internal/UShortArraySerializer : kotlinx/serialization/internal/PrimitiveArraySerializer, kotlinx/serialization/KSerializer { + public static final field INSTANCE Lkotlinx/serialization/internal/UShortArraySerializer; + public synthetic fun collectionSize (Ljava/lang/Object;)I + public synthetic fun empty ()Ljava/lang/Object; + public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V + public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILkotlinx/serialization/internal/PrimitiveArrayBuilder;Z)V + public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun writeContent (Lkotlinx/serialization/encoding/CompositeEncoder;Ljava/lang/Object;I)V +} + public final class kotlinx/serialization/internal/UShortSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lkotlinx/serialization/internal/UShortSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; @@ -1177,6 +1285,7 @@ public final class kotlinx/serialization/internal/UnitSerializer : kotlinx/seria public final class kotlinx/serialization/modules/PolymorphicModuleBuilder { public fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V + public synthetic fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun buildTo (Lkotlinx/serialization/modules/SerializersModuleBuilder;)V public final fun default (Lkotlin/jvm/functions/Function1;)V public final fun defaultDeserializer (Lkotlin/jvm/functions/Function1;)V @@ -1205,6 +1314,7 @@ public final class kotlinx/serialization/modules/SerializersModuleBuilder : kotl } public final class kotlinx/serialization/modules/SerializersModuleBuildersKt { + public static final fun EmptySerializersModule ()Lkotlinx/serialization/modules/SerializersModule; public static final fun SerializersModule (Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/modules/SerializersModule; public static final fun polymorphic (Lkotlinx/serialization/modules/SerializersModuleBuilder;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function1;)V public static synthetic fun polymorphic$default (Lkotlinx/serialization/modules/SerializersModuleBuilder;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V diff --git a/core/build.gradle b/core/build.gradle index 900c494f..f52837ac 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile + /* * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @@ -25,7 +27,7 @@ kotlin { These manifest values help kotlinx.serialization compiler plugin determine if it is compatible with a given runtime library. Plugin reads them during compilation. - Implementation-Version is used to determine whether runtime library supports a given plugin feature (e.g. inline classes serialization + Implementation-Version is used to determine whether runtime library supports a given plugin feature (e.g. value classes serialization in Kotlin 1.x may require runtime library version 1.y to work). Compiler plugin may enable or disable features by looking on Implementation-Version. @@ -35,6 +37,26 @@ kotlin { to reject runtime if runtime's Require-Kotlin-Version is greater then the current compiler. */ tasks.withType(Jar).named(kotlin.jvm().artifactsTaskName) { + + // adding the ProGuard rules to the jar + from(rootProject.file("rules/common.pro")) { + rename { "kotlinx-serialization-common.pro" } + into("META-INF/proguard") + } + from(rootProject.file("rules/common.pro")) { + rename { "kotlinx-serialization-common.pro" } + into("META-INF/com.android.tools/proguard") + } + from(rootProject.file("rules/common.pro")) { + rename { "kotlinx-serialization-common.pro" } + into("META-INF/com.android.tools/r8") + } + from(rootProject.file("rules/r8.pro")) { + rename { "kotlinx-serialization-r8.pro" } + into("META-INF/com.android.tools/r8") + } + + manifest { attributes( "Implementation-Version": version, @@ -44,3 +66,7 @@ tasks.withType(Jar).named(kotlin.jvm().artifactsTaskName) { } Java9Modularity.configureJava9ModuleInfo(project) + +tasks.withType(org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink.class).configureEach { + kotlinOptions.freeCompilerArgs += "-Xwasm-enable-array-range-checks" +} diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt index 45b5c1c0..081ee827 100644 --- a/core/commonMain/src/kotlinx/serialization/Annotations.kt +++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt @@ -1,9 +1,7 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING") // Parameters of annotations should probably be ignored, too - package kotlinx.serialization import kotlinx.serialization.descriptors.* @@ -67,6 +65,7 @@ import kotlin.reflect.* * @see UseSerializers * @see Serializer */ +@MustBeDocumented @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS, AnnotationTarget.TYPE) //@Retention(AnnotationRetention.RUNTIME) // Runtime is the default retention, also see KT-41082 public annotation class Serializable( @@ -74,6 +73,36 @@ public annotation class Serializable( ) /** + * The meta-annotation for adding [Serializable] behaviour to user-defined annotations. + * + * Applying [MetaSerializable] to the annotation class `A` instructs the serialization plugin to treat annotation A + * as [Serializable]. In addition, all annotations marked with [MetaSerializable] are saved in the generated [SerialDescriptor] + * as if they are annotated with [SerialInfo]. + * + * ``` + * @MetaSerializable + * @Target(AnnotationTarget.CLASS) + * annotation class MySerializable(val data: String) + * + * @MySerializable("some_data") + * class MyData(val myData: AnotherData, val intProperty: Int, ...) + * + * val serializer = MyData.serializer() + * serializer.descriptor.annotations.filterIsInstance<MySerializable>().first().data // <- returns "some_data" + * ``` + * + * @see Serializable + * @see SerialInfo + * @see UseSerializers + * @see Serializer + */ +@MustBeDocumented +@Target(AnnotationTarget.ANNOTATION_CLASS) +//@Retention(AnnotationRetention.RUNTIME) // Runtime is the default retention, also see KT-41082 +@ExperimentalSerializationApi +public annotation class MetaSerializable + +/** * Instructs the serialization plugin to turn this class into serializer for specified class [forClass]. * However, it would not be used automatically. To apply it on particular class or property, * use [Serializable] or [UseSerializers], or [Contextual] with runtime registration. @@ -82,6 +111,7 @@ public annotation class Serializable( * Changes may include additional constraints on classes and objects marked with this annotation, * behavioural changes and even serialized shape of the class. */ +@MustBeDocumented @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) @ExperimentalSerializationApi @@ -113,7 +143,11 @@ public annotation class Serializer( * // Prints "{"int":42}" * println(Json.encodeToString(CustomName(42))) * ``` + * + * If a name of class or property is overridden with this annotation, original source code name is not available for the library. + * Tools like `JsonNamingStrategy` and `ProtoBufSchemaGenerator` would see and transform [value] from [SerialName] annotation. */ +@MustBeDocumented @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) // @Retention(AnnotationRetention.RUNTIME) still runtime, but KT-41082 public annotation class SerialName(val value: String) @@ -121,14 +155,16 @@ public annotation class SerialName(val value: String) /** * Indicates that property must be present during deserialization process, despite having a default value. */ +@MustBeDocumented @Target(AnnotationTarget.PROPERTY) // @Retention(AnnotationRetention.RUNTIME) still runtime, but KT-41082 public annotation class Required /** * Marks this property invisible for the whole serialization process, including [serial descriptors][SerialDescriptor]. - * Transient properties should have default values. + * Transient properties must have default values. */ +@MustBeDocumented @Target(AnnotationTarget.PROPERTY) // @Retention(AnnotationRetention.RUNTIME) still runtime, but KT-41082 public annotation class Transient @@ -154,6 +190,7 @@ public annotation class Transient * @see EncodeDefault.Mode.ALWAYS * @see EncodeDefault.Mode.NEVER */ +@MustBeDocumented @Target(AnnotationTarget.PROPERTY) @ExperimentalSerializationApi public annotation class EncodeDefault(val mode: Mode = Mode.ALWAYS) { @@ -188,6 +225,7 @@ public annotation class EncodeDefault(val mode: Mode = Mode.ALWAYS) { * Keep in mind that Kotlin compiler prioritizes [function parameter target][AnnotationTarget.VALUE_PARAMETER] over [property target][AnnotationTarget.PROPERTY], * so serial info annotations used on constructor-parameters-as-properties without explicit declaration-site or use-site target are not preserved. */ +@MustBeDocumented @Target(AnnotationTarget.ANNOTATION_CLASS) @Retention(AnnotationRetention.BINARY) @ExperimentalSerializationApi @@ -225,12 +263,12 @@ public annotation class SerialInfo * fun foo(): Int = Derived.serializer().descriptor.annotations.filterIsInstance<A>().single().value * ``` */ +@MustBeDocumented @Target(AnnotationTarget.ANNOTATION_CLASS) @Retention(AnnotationRetention.BINARY) @ExperimentalSerializationApi public annotation class InheritableSerialInfo - /** * Instructs the plugin to use [ContextualSerializer] on a given property or type. * Context serializer is usually used when serializer for type can only be found in runtime. @@ -287,6 +325,23 @@ public annotation class UseSerializers(vararg val serializerClasses: KClass<out public annotation class Polymorphic /** + * Instructs the serialization plugin to keep automatically generated implementation of [KSerializer] + * for the current class if a custom serializer is specified at the same time `@Serializable(with=SomeSerializer::class)`. + * + * Automatically generated serializer is available via `generatedSerializer()` function in companion object of serializable class. + * + * Generated serializers allow to use custom serializers on classes from which other serializable classes are inherited. + * + * Used only with the [Serializable] annotation. + * + * A compiler version `2.0.0` and higher is required. + */ +@InternalSerializationApi +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +public annotation class KeepGeneratedSerializer + +/** * Marks declarations that are still **experimental** in kotlinx.serialization, which means that the design of the * corresponding declarations has open issues which may (or may not) lead to their changes in the future. * Roughly speaking, there is a chance that those declarations will be deprecated in the near future or diff --git a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt index 53fd4c30..20e9ce1c 100644 --- a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt @@ -29,7 +29,7 @@ import kotlin.reflect.* * @Serializable * class ClassWithDate(val data: String, @Contextual val timestamp: Date) * - * val moduleForDate = serializersModule(MyISO8601DateSerializer) + * val moduleForDate = serializersModuleOf(MyISO8601DateSerializer) * val json = Json { serializersModule = moduleForDate } * json.encodeToString(ClassWithDate("foo", Date()) * ``` diff --git a/core/commonMain/src/kotlinx/serialization/KSerializer.kt b/core/commonMain/src/kotlinx/serialization/KSerializer.kt index 3b6c8697..89107bba 100644 --- a/core/commonMain/src/kotlinx/serialization/KSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/KSerializer.kt @@ -5,7 +5,6 @@ package kotlinx.serialization import kotlinx.serialization.descriptors.* -import kotlinx.serialization.descriptors.elementNames import kotlinx.serialization.encoding.* /** @@ -51,6 +50,16 @@ import kotlinx.serialization.encoding.* * ``` * * Deserialization process is symmetric and uses [Decoder]. + * + * ### Exception types for `KSerializer` implementation + * + * Implementations of [serialize] and [deserialize] methods are allowed to throw + * any subtype of [IllegalArgumentException] in order to indicate serialization + * and deserialization errors. + * + * For serializer implementations, it is recommended to throw subclasses of [SerializationException] for + * any serialization-specific errors related to invalid or unsupported format of the data + * and [IllegalStateException] for errors during validation of the data. */ public interface KSerializer<T> : SerializationStrategy<T>, DeserializationStrategy<T> { /** @@ -106,6 +115,10 @@ public interface SerializationStrategy<in T> { * // don't encode 'alwaysZero' property because we decided to do so * } // end of the structure * ``` + * + * @throws SerializationException in case of any serialization-specific error + * @throws IllegalArgumentException if the supplied input does not comply encoder's specification + * @see KSerializer for additional information about general contracts and exception specifics */ public fun serialize(encoder: Encoder, value: T) } @@ -126,7 +139,7 @@ public interface SerializationStrategy<in T> { * * For a more detailed explanation of the serialization process, please refer to [KSerializer] documentation. */ -public interface DeserializationStrategy<T> { +public interface DeserializationStrategy<out T> { /** * Describes the structure of the serializable representation of [T], that current * deserializer is able to deserialize. @@ -171,7 +184,11 @@ public interface DeserializationStrategy<T> { * return MyData(int, list, alwaysZero = 0L) * } * ``` + * + * @throws MissingFieldException if non-optional fields were not found during deserialization + * @throws SerializationException in case of any deserialization-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] + * @see KSerializer for additional information about general contracts and exception specifics */ public fun deserialize(decoder: Decoder): T } - diff --git a/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt index 311f809d..6ee70717 100644 --- a/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt @@ -98,7 +98,7 @@ public class PolymorphicSerializer<T : Any>(override val baseClass: KClass<T>) : public fun <T : Any> AbstractPolymorphicSerializer<T>.findPolymorphicSerializer( decoder: CompositeDecoder, klassName: String? -): DeserializationStrategy<out T> = +): DeserializationStrategy<T> = findPolymorphicSerializerOrNull(decoder, klassName) ?: throwSubtypeNotRegistered(klassName, baseClass) @InternalSerializationApi diff --git a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt index 354229ff..52b7c054 100644 --- a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt @@ -105,9 +105,9 @@ public class SealedClassSerializer<T : Any>( element("type", String.serializer().descriptor) val elementDescriptor = buildSerialDescriptor("kotlinx.serialization.Sealed<${baseClass.simpleName}>", SerialKind.CONTEXTUAL) { - subclassSerializers.forEach { - val d = it.descriptor - element(d.serialName, d) + // serialName2Serializer is guaranteed to have no duplicates — checked in `init`. + serialName2Serializer.forEach { (name, serializer) -> + element(name, serializer.descriptor) } } element("value", elementDescriptor) @@ -123,6 +123,9 @@ public class SealedClassSerializer<T : Any>( throw IllegalArgumentException("All subclasses of sealed class ${baseClass.simpleName} should be marked @Serializable") } + // Note: we do not check whether different serializers are provided if the same KClass duplicated in the `subclasses`. + // Plugin should produce identical serializers, although they are not always strictly equal (e.g. new ObjectSerializer + // may be created every time) class2Serializer = subclasses.zip(subclassSerializers).toMap() serialName2Serializer = class2Serializer.entries.groupingBy { it.value.descriptor.serialName } .aggregate<Map.Entry<KClass<out T>, KSerializer<out T>>, String, Map.Entry<KClass<*>, KSerializer<out T>>> @@ -137,7 +140,10 @@ public class SealedClassSerializer<T : Any>( }.mapValues { it.value.value } } - override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): DeserializationStrategy<out T>? { + override fun findPolymorphicSerializerOrNull( + decoder: CompositeDecoder, + klassName: String? + ): DeserializationStrategy<T>? { return serialName2Serializer[klassName] ?: super.findPolymorphicSerializerOrNull(decoder, klassName) } diff --git a/core/commonMain/src/kotlinx/serialization/SerialFormat.kt b/core/commonMain/src/kotlinx/serialization/SerialFormat.kt index e4801a0a..11234b2a 100644 --- a/core/commonMain/src/kotlinx/serialization/SerialFormat.kt +++ b/core/commonMain/src/kotlinx/serialization/SerialFormat.kt @@ -19,6 +19,16 @@ import kotlinx.serialization.modules.* * Typically, formats have their specific [Encoder] and [Decoder] implementations * as private classes and do not expose them. * + * ### Exception types for `SerialFormat` implementation + * + * Methods responsible for format-specific encoding and decoding are allowed to throw + * any subtype of [IllegalArgumentException] in order to indicate serialization + * and deserialization errors. It is recommended to throw subtypes of [SerializationException] + * for encoder and decoder specific errors and [IllegalArgumentException] for input + * and output validation-specific errors. + * + * For formats + * * ### Not stable for inheritance * * `SerialFormat` interface is not stable for inheritance in 3rd party libraries, as new methods @@ -49,11 +59,17 @@ public interface BinaryFormat : SerialFormat { /** * Serializes and encodes the given [value] to byte array using the given [serializer]. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ public fun <T> encodeToByteArray(serializer: SerializationStrategy<T>, value: T): ByteArray /** - * Decodes and deserializes the given [byte array][bytes] to the value of type [T] using the given [deserializer] + * Decodes and deserializes the given [byte array][bytes] to the value of type [T] using the given [deserializer]. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ public fun <T> decodeFromByteArray(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T } @@ -72,27 +88,37 @@ public interface StringFormat : SerialFormat { /** * Serializes and encodes the given [value] to string using the given [serializer]. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ public fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String /** - * Decodes and deserializes the given [string] to the value of type [T] using the given [deserializer] + * Decodes and deserializes the given [string] to the value of type [T] using the given [deserializer]. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ public fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T } /** * Serializes and encodes the given [value] to string using serializer retrieved from the reified type parameter. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ -@OptIn(ExperimentalSerializationApi::class) public inline fun <reified T> StringFormat.encodeToString(value: T): String = encodeToString(serializersModule.serializer(), value) /** * Decodes and deserializes the given [string] to the value of type [T] using deserializer * retrieved from the reified type parameter. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ -@OptIn(ExperimentalSerializationApi::class) public inline fun <reified T> StringFormat.decodeFromString(string: String): T = decodeFromString(serializersModule.serializer(), string) @@ -104,8 +130,10 @@ public inline fun <reified T> StringFormat.decodeFromString(string: String): T = * Hex representation does not interfere with serialization and encoding process of the format and * only applies transformation to the resulting array. It is recommended to use for debugging and * testing purposes. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ -@OptIn(ExperimentalSerializationApi::class) public fun <T> BinaryFormat.encodeToHexString(serializer: SerializationStrategy<T>, value: T): String = InternalHexConverter.printHexBinary(encodeToByteArray(serializer, value), lowerCase = true) @@ -113,9 +141,11 @@ public fun <T> BinaryFormat.encodeToHexString(serializer: SerializationStrategy< * Decodes byte array from the given [hex] string and the decodes and deserializes it * to the value of type [T], delegating it to the [BinaryFormat]. * - * This method is a counterpart to [encodeToHexString] + * This method is a counterpart to [encodeToHexString]. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ -@OptIn(ExperimentalSerializationApi::class) public fun <T> BinaryFormat.decodeFromHexString(deserializer: DeserializationStrategy<T>, hex: String): T = decodeFromByteArray(deserializer, InternalHexConverter.parseHexBinary(hex)) @@ -126,8 +156,10 @@ public fun <T> BinaryFormat.decodeFromHexString(deserializer: DeserializationStr * Hex representation does not interfere with serialization and encoding process of the format and * only applies transformation to the resulting array. It is recommended to use for debugging and * testing purposes. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ -@OptIn(ExperimentalSerializationApi::class) public inline fun <reified T> BinaryFormat.encodeToHexString(value: T): String = encodeToHexString(serializersModule.serializer(), value) @@ -135,24 +167,30 @@ public inline fun <reified T> BinaryFormat.encodeToHexString(value: T): String = * Decodes byte array from the given [hex] string and the decodes and deserializes it * to the value of type [T], delegating it to the [BinaryFormat]. * - * This method is a counterpart to [encodeToHexString] + * This method is a counterpart to [encodeToHexString]. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ -@OptIn(ExperimentalSerializationApi::class) public inline fun <reified T> BinaryFormat.decodeFromHexString(hex: String): T = decodeFromHexString(serializersModule.serializer(), hex) /** * Serializes and encodes the given [value] to byte array using serializer * retrieved from the reified type parameter. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ -@OptIn(ExperimentalSerializationApi::class) public inline fun <reified T> BinaryFormat.encodeToByteArray(value: T): ByteArray = encodeToByteArray(serializersModule.serializer(), value) /** * Decodes and deserializes the given [byte array][bytes] to the value of type [T] using deserializer * retrieved from the reified type parameter. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ -@OptIn(ExperimentalSerializationApi::class) public inline fun <reified T> BinaryFormat.decodeFromByteArray(bytes: ByteArray): T = decodeFromByteArray(serializersModule.serializer(), bytes) diff --git a/core/commonMain/src/kotlinx/serialization/SerializationException.kt b/core/commonMain/src/kotlinx/serialization/SerializationException.kt deleted file mode 100644 index 41631f50..00000000 --- a/core/commonMain/src/kotlinx/serialization/SerializationException.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.serialization - -/** - * A generic exception indicating the problem in serialization or deserialization process. - * This is a generic exception type that can be thrown during the problem at any stage of the serialization, - * including encoding, decoding, serialization, deserialization. - * [SerialFormat] implementors should throw subclasses of this exception at any unexpected event, - * whether it is a malformed input or unsupported class layout. - */ -public open class SerializationException : IllegalArgumentException { - /* - * Rationale behind making it IllegalArgumentException: - * Any serialization exception is triggered by the illegal argument, whether - * it is a serializer that does not support specific structure or an invalid input. - * Making it IAE just aligns the implementation with this fact. - * - * Another point is input validation. The simplest way to validate - * deserialized data is `require` in `init` block: - * ``` - * @Serializable class Foo(...) { - * init { - * required(age > 0) { ... } - * require(name.isNotBlank()) { ... } - * } - * } - * ``` - * While clearly being serialization error (when compromised data was deserialized), - * Kotlin way is to throw IAE here instead of using library-specific SerializationException. - * - * Also, any production-grade system has a general try-catch around deserialization of potentially - * untrusted/invalid/corrupted data with the corresponding logging, error reporting and diagnostic. - * Such handling should catch some subtype of exception (e.g. it's unlikely that catching OOM is desirable). - * Taking it into account, it becomes clear that SE should be subtype of IAE. - */ - - /** - * Creates an instance of [SerializationException] without any details. - */ - public constructor() - - /** - * Creates an instance of [SerializationException] with the specified detail [message]. - */ - public constructor(message: String?) : super(message) - - /** - * Creates an instance of [SerializationException] with the specified detail [message], and the given [cause]. - */ - public constructor(message: String?, cause: Throwable?) : super(message, cause) - - /** - * Creates an instance of [SerializationException] with the specified [cause]. - */ - public constructor(cause: Throwable?) : super(cause) -} - -/** - * Thrown when [KSerializer] did not receive property from [Decoder], and this property was not optional. - */ -@PublishedApi -internal class MissingFieldException -// This constructor is used by coroutines exception recovery -internal constructor(message: String?, cause: Throwable?) : SerializationException(message, cause) { - // This constructor is used by the generated serializers - constructor(fieldName: String) : this("Field '$fieldName' is required, but it was missing", null) - internal constructor(fieldNames: List<String>, serialName: String) : this(if (fieldNames.size == 1) "Field '${fieldNames[0]}' is required for type with serial name '$serialName', but it was missing" else "Fields $fieldNames are required for type with serial name '$serialName', but they were missing", null) -} - -/** - * Thrown when [KSerializer] received unknown property index from [CompositeDecoder.decodeElementIndex]. - * - * This exception means that data schema has changed in backwards-incompatible way. - */ -@PublishedApi -internal class UnknownFieldException -// This constructor is used by coroutines exception recovery -internal constructor(message: String?) : SerializationException(message) { - // This constructor is used by the generated serializers - constructor(index: Int) : this("An unknown field for index $index") -} diff --git a/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt b/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt new file mode 100644 index 00000000..99f7d0a7 --- /dev/null +++ b/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization + +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* + +/** + * A generic exception indicating the problem in serialization or deserialization process. + * + * This is a generic exception type that can be thrown during problems at any stage of the serialization, + * including encoding, decoding, serialization, deserialization, and validation. + * [SerialFormat] implementors should throw subclasses of this exception at any unexpected event, + * whether it is a malformed input or unsupported class layout. + * + * [SerializationException] is a subclass of [IllegalArgumentException] for the sake of consistency and user-defined validation: + * Any serialization exception is triggered by the illegal input, whether + * it is a serializer that does not support specific structure or an invalid input. + * + * It is also an established pattern to validate input in user's classes in the following manner: + * ``` + * @Serializable + * class Foo(...) { + * init { + * required(age > 0) { ... } + * require(name.isNotBlank()) { ... } + * } + * } + * ``` + * While clearly being serialization error (when compromised data was deserialized), + * Kotlin way is to throw `IllegalArgumentException` here instead of using library-specific `SerializationException`. + * + * For general "catch-all" patterns around deserialization of potentially + * untrusted/invalid/corrupted data it is recommended to catch `IllegalArgumentException` type + * to avoid catching irrelevant to serialization errors such as `OutOfMemoryError` or domain-specific ones. + */ +public open class SerializationException : IllegalArgumentException { + + /** + * Creates an instance of [SerializationException] without any details. + */ + public constructor() + + /** + * Creates an instance of [SerializationException] with the specified detail [message]. + */ + public constructor(message: String?) : super(message) + + /** + * Creates an instance of [SerializationException] with the specified detail [message], and the given [cause]. + */ + public constructor(message: String?, cause: Throwable?) : super(message, cause) + + /** + * Creates an instance of [SerializationException] with the specified [cause]. + */ + public constructor(cause: Throwable?) : super(cause) +} + +/** + * Thrown when [KSerializer] did not receive a non-optional property from [CompositeDecoder] and [CompositeDecoder.decodeElementIndex] + * had already returned [CompositeDecoder.DECODE_DONE]. + * + * [MissingFieldException] is thrown on missing field from all [auto-generated][Serializable] serializers and it + * is recommended to throw this exception from user-defined serializers. + * + * [MissingFieldException] is constructed from the following properties: + * - [missingFields] -- fields that were required for the deserialization but have not been found. + * They are always non-empty and their names match the corresponding names in [SerialDescriptor.elementNames] + * - Optional `serialName` -- serial name of the enclosing class that failed to get deserialized. + * Matches the corresponding [SerialDescriptor.serialName]. + * + * @see SerializationException + * @see KSerializer + */ +@ExperimentalSerializationApi +public class MissingFieldException( + missingFields: List<String>, message: String?, cause: Throwable? +) : SerializationException(message, cause) { + + /** + * List of fields that were required but not found during deserialization. + * Contains at least one element. + */ + public val missingFields: List<String> = missingFields + + /** + * Creates an instance of [MissingFieldException] for the given [missingFields] and [serialName] of + * the corresponding serializer. + */ + public constructor( + missingFields: List<String>, + serialName: String + ) : this( + missingFields, + if (missingFields.size == 1) "Field '${missingFields[0]}' is required for type with serial name '$serialName', but it was missing" + else "Fields $missingFields are required for type with serial name '$serialName', but they were missing", + null + ) + + /** + * Creates an instance of [MissingFieldException] for the given [missingField] and [serialName] of + * the corresponding serializer. + */ + public constructor( + missingField: String, + serialName: String + ) : this( + listOf(missingField), + "Field '$missingField' is required for type with serial name '$serialName', but it was missing", + null + ) + + @PublishedApi // Constructor used by the generated serializers + internal constructor(missingField: String) : this( + listOf(missingField), + "Field '$missingField' is required, but it was missing", + null + ) +} + +/** + * Thrown when [KSerializer] received unknown property index from [CompositeDecoder.decodeElementIndex]. + * + * This exception means that data schema has changed in backwards-incompatible way. + */ +@PublishedApi +internal class UnknownFieldException +// This constructor is used by coroutines exception recovery +internal constructor(message: String?) : SerializationException(message) { + // This constructor is used by the generated serializers + constructor(index: Int) : this("An unknown field for index $index") +} 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() +} diff --git a/core/commonMain/src/kotlinx/serialization/SerializersCache.kt b/core/commonMain/src/kotlinx/serialization/SerializersCache.kt new file mode 100644 index 00000000..cc86e435 --- /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(serializers) { types[0].classifier } +} + +/** + * 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(serializers) { types[0].classifier }?.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/builtins/BuiltinSerializers.kt b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt index 147f25d9..4bd81012 100644 --- a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt +++ b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt @@ -9,6 +9,7 @@ import kotlinx.serialization.* import kotlinx.serialization.internal.* import kotlin.reflect.* import kotlinx.serialization.descriptors.* +import kotlin.time.Duration /** * Returns a nullable serializer for the given serializer of non-null type. @@ -33,7 +34,7 @@ public fun <K, V> PairSerializer( * Returns built-in serializer for [Map.Entry]. * Resulting serializer represents entry as a structure with a single key-value pair. * E.g. `Pair(1, 2)` and `Map.Entry(1, 2)` will be serialized to JSON as - * `{"first": 1, "second": 2}` and {"1": 2} respectively. + * `{"first": 1, "second": 2}` and `{"1": 2}` respectively. */ public fun <K, V> MapEntrySerializer( keySerializer: KSerializer<K>, @@ -74,6 +75,14 @@ public fun Byte.Companion.serializer(): KSerializer<Byte> = ByteSerializer public fun ByteArraySerializer(): KSerializer<ByteArray> = ByteArraySerializer /** + * Returns serializer for [UByteArray] with [descriptor][SerialDescriptor] of [StructureKind.LIST] kind. + * Each element of the array is serialized one by one with [UByte.Companion.serializer]. + */ +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +public fun UByteArraySerializer(): KSerializer<UByteArray> = UByteArraySerializer + +/** * Returns serializer for [Short] with [descriptor][SerialDescriptor] of [PrimitiveKind.SHORT] kind. */ public fun Short.Companion.serializer(): KSerializer<Short> = ShortSerializer @@ -85,6 +94,14 @@ public fun Short.Companion.serializer(): KSerializer<Short> = ShortSerializer public fun ShortArraySerializer(): KSerializer<ShortArray> = ShortArraySerializer /** + * Returns serializer for [UShortArray] with [descriptor][SerialDescriptor] of [StructureKind.LIST] kind. + * Each element of the array is serialized one by one with [UShort.Companion.serializer]. + */ +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +public fun UShortArraySerializer(): KSerializer<UShortArray> = UShortArraySerializer + +/** * Returns serializer for [Int] with [descriptor][SerialDescriptor] of [PrimitiveKind.INT] kind. */ public fun Int.Companion.serializer(): KSerializer<Int> = IntSerializer @@ -96,6 +113,14 @@ public fun Int.Companion.serializer(): KSerializer<Int> = IntSerializer public fun IntArraySerializer(): KSerializer<IntArray> = IntArraySerializer /** + * Returns serializer for [UIntArray] with [descriptor][SerialDescriptor] of [StructureKind.LIST] kind. + * Each element of the array is serialized one by one with [UInt.Companion.serializer]. + */ +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +public fun UIntArraySerializer(): KSerializer<UIntArray> = UIntArraySerializer + +/** * Returns serializer for [Long] with [descriptor][SerialDescriptor] of [PrimitiveKind.LONG] kind. */ public fun Long.Companion.serializer(): KSerializer<Long> = LongSerializer @@ -107,6 +132,14 @@ public fun Long.Companion.serializer(): KSerializer<Long> = LongSerializer public fun LongArraySerializer(): KSerializer<LongArray> = LongArraySerializer /** + * Returns serializer for [ULongArray] with [descriptor][SerialDescriptor] of [StructureKind.LIST] kind. + * Each element of the array is serialized one by one with [ULong.Companion.serializer]. + */ +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +public fun ULongArraySerializer(): KSerializer<ULongArray> = ULongArraySerializer + +/** * Returns serializer for [Float] with [descriptor][SerialDescriptor] of [PrimitiveKind.FLOAT] kind. */ public fun Float.Companion.serializer(): KSerializer<Float> = FloatSerializer @@ -193,27 +226,36 @@ public fun <K, V> MapSerializer( /** * Returns serializer for [UInt]. */ -@ExperimentalSerializationApi -@ExperimentalUnsignedTypes public fun UInt.Companion.serializer(): KSerializer<UInt> = UIntSerializer /** * Returns serializer for [ULong]. */ -@ExperimentalSerializationApi -@ExperimentalUnsignedTypes public fun ULong.Companion.serializer(): KSerializer<ULong> = ULongSerializer /** * Returns serializer for [UByte]. */ -@ExperimentalSerializationApi -@ExperimentalUnsignedTypes public fun UByte.Companion.serializer(): KSerializer<UByte> = UByteSerializer /** * Returns serializer for [UShort]. */ -@ExperimentalSerializationApi -@ExperimentalUnsignedTypes public fun UShort.Companion.serializer(): KSerializer<UShort> = UShortSerializer + +/** + * Returns serializer for [Duration]. + * It is serialized as a string that represents a duration in the ISO-8601-2 format. + * + * The result of serialization is similar to calling [Duration.toIsoString], for deserialization is [Duration.parseIsoString]. + */ +public fun Duration.Companion.serializer(): KSerializer<Duration> = DurationSerializer + +/** + * Returns serializer for [Nothing]. + * Throws an exception when trying to encode or decode. + * + * It is used as a dummy in case it is necessary to pass a type to a parameterized class. At the same time, it is expected that this generic type will not participate in serialization. + */ +@ExperimentalSerializationApi +public fun NothingSerializer(): KSerializer<Nothing> = NothingSerializer diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt index 136df8c3..17fdbfe0 100644 --- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt +++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt @@ -5,11 +5,12 @@ package kotlinx.serialization.descriptors import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.encoding.* /** * Serial descriptor is an inherent property of [KSerializer] that describes the structure of the serializable type. - * The structure of the serializable type is not only the property of the type, but also of the serializer as well, + * The structure of the serializable type is not only the characteristic of the type itself, but also of the serializer as well, * meaning that one type can have multiple descriptors that have completely different structure. * * For example, the class `class Color(val rgb: Int)` can have multiple serializable representations, @@ -25,19 +26,19 @@ import kotlinx.serialization.encoding.* * For generic types, the actual type substitution is omitted from the string representation and the name * identifies the family of the serializers without type substitutions. However, type substitution is accounted * in [equals] and [hashCode] operations, meaning that descriptors of generic classes with the same name, but different type - * parameters, are not equal to each other. + * arguments, are not equal to each other. * [serialName] is typically used to specify the type of the target class during serialization of polymorphic and sealed * classes, for observability and diagnostics. - * * [Kind][SerialKind] defines what this descriptor represents: primitive, enum, object, collection et cetera. + * * [Kind][SerialKind] defines what this descriptor represents: primitive, enum, object, collection etc. * * Children elements are represented as serial descriptors as well and define the structure of the type's elements. - * * Metadata carries additional potentially useful information, such as [nullability][nullable], [optionality][isElementOptional] + * * Metadata carries additional information, such as [nullability][nullable], [optionality][isElementOptional] * and [serial annotations][getElementAnnotations]. * * ### Usages * There are two general usages of the descriptors: THE serialization process and serialization introspection. * * #### Serialization - * Serial descriptor is used as bridge between decoders/encoders and serializers. + * Serial descriptor is used as a bridge between decoders/encoders and serializers. * When asking for a next element, the serializer provides an expected descriptor to the decoder, and, * based on the descriptor content, decoder decides how to parse its input. * In JSON, for example, when the encoder is asked to encode the next element and this element @@ -59,15 +60,15 @@ import kotlinx.serialization.encoding.* * the range from zero to [elementsCount] and represent and index of the property in this class. * Consequently, primitives do not have children and their element count is zero. * - * For collections and maps, though, indices does not have fixed bound. Regular collections descriptors usually + * For collections and maps indices don't have fixed bound. Regular collections descriptors usually * have one element (`T`, maps have two, one for keys and one for values), but potentially unlimited * number of actual children values. Valid indices range is not known statically - * and implementations of descriptor should provide consistent and unbounded names and indices. + * and implementations of such descriptor should provide consistent and unbounded names and indices. * * In practice, for regular classes it is allowed to invoke `getElement*(index)` methods - * with an index within `0 until elementsCount` range and element at the particular index corresponds to the + * with an index from `0` to [elementsCount] range and element at the particular index corresponds to the * serializable property at the given position. - * For collections and maps, index parameter for `getElement*(index)` methods is effectively bound + * For collections and maps, index parameter for `getElement*(index)` methods is effectively bounded * by the maximal number of collection/map elements. * * ### Thread-safety and mutability @@ -76,7 +77,6 @@ import kotlinx.serialization.encoding.* * ### Equality and caching * Serial descriptor can be used as a unique identifier for format-specific data or schemas and * this implies the following restrictions on its `equals` and `hashCode`: - * * * * An [equals] implementation should use both [serialName] and elements structure. * Comparing [elementDescriptors] directly is discouraged, @@ -100,8 +100,8 @@ import kotlinx.serialization.encoding.* * [hashCode] implementation should use the same properties for computing the result. * * ### User-defined serial descriptors - * The best way to define a custom descriptor is to use [SerialDescriptor] builder function, where - * for each serializable property corresponding element is declared. + * The best way to define a custom descriptor is to use [buildClassSerialDescriptor] builder function, where + * for each serializable property the corresponding element is declared. * * Example: * ``` @@ -113,15 +113,29 @@ import kotlinx.serialization.encoding.* * ) * * // Descriptor for such class: - * SerialDescriptor("my.package.Data") { + * buildClassSerialDescriptor("my.package.Data") { * // intField is deliberately ignored by serializer -- not present in the descriptor as well * element<Long>("_longField") // longField is named as _longField - * element("stringField", listDescriptor<String>()) + * element("stringField", listSerialDescriptor<String>()) + * } + * + * // Example of 'serialize' function for such descriptor + * override fun serialize(encoder: Encoder, value: Data) { + * encoder.encodeStructure(descriptor) { + * encodeLongElement(descriptor, 0, value.longField) // Will be written as "_longField" because descriptor's child at index 0 says so + * encodeSerializableElement(descriptor, 1, ListSerializer(String.serializer()), value.stringList) + * } * } * ``` * * For a classes that are represented as a single primitive value, [PrimitiveSerialDescriptor] builder function can be used instead. * + * ### Consistency violations + * An implementation of [SerialDescriptor] should be consistent with the implementation of the corresponding [KSerializer]. + * Yet it is not type-checked statically, thus making it possible to declare a non-consistent implementations of descriptor and serializer. + * In such cases, the behaviour of an underlying format is unspecified and may lead to both runtime errors and encoding of + * corrupted data that is impossible to decode back. + * * ### Not stable for inheritance * * `SerialDescriptor` interface is not stable for inheritance in 3rd party libraries, as new methods @@ -162,9 +176,9 @@ public interface SerialDescriptor { public val isNullable: Boolean get() = false /** - * Returns `true` if this descriptor describes a serializable inline class. + * Returns `true` if this descriptor describes a serializable value class which underlying value + * is serialized directly. */ - @ExperimentalSerializationApi public val isInline: Boolean get() = false /** @@ -245,7 +259,7 @@ public interface SerialDescriptor { public fun getElementDescriptor(index: Int): SerialDescriptor /** - * Whether the element at the given [index] is optional (can be absent is serialized form). + * Whether the element at the given [index] is optional (can be absent in serialized form). * For generated descriptors, all elements that have a corresponding default parameter value are * marked as optional. Custom serializers can treat optional values in a serialization-specific manner * without default parameters constraint. diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt index f9e51737..cb380aaf 100644 --- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt +++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt @@ -5,6 +5,7 @@ package kotlinx.serialization.descriptors import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.encoding.* import kotlinx.serialization.internal.* import kotlin.reflect.* @@ -24,22 +25,24 @@ import kotlin.reflect.* * val nullableInt: Int? * ) * // Descriptor for such class: - * SerialDescriptor("my.package.Data") { + * buildClassSerialDescriptor("my.package.Data") { * // intField is deliberately ignored by serializer -- not present in the descriptor as well * element<Long>("_longField") // longField is named as _longField - * element("stringField", listDescriptor<String>()) - * element("nullableInt", descriptor<Int>().nullable) + * element("stringField", listSerialDescriptor<String>()) // or ListSerializer(String.serializer()).descriptor + * element("nullableInt", serialDescriptor<Int>().nullable) * } * ``` * * Example for generic classes: * ``` + * import kotlinx.serialization.builtins.* + * * @Serializable(CustomSerializer::class) * class BoxedList<T>(val list: List<T>) * * class CustomSerializer<T>(tSerializer: KSerializer<T>): KSerializer<BoxedList<T>> { * // here we use tSerializer.descriptor because it represents T - * override val descriptor = SerialDescriptor("pkg.BoxedList", CLASS, tSerializer.descriptor) { + * override val descriptor = buildClassSerialDescriptor("pkg.BoxedList", tSerializer.descriptor) { * // here we have to wrap it with List first, because property has type List<T> * element("list", ListSerializer(tSerializer).descriptor) // or listSerialDescriptor(tSerializer.descriptor) * } @@ -71,7 +74,7 @@ public fun buildClassSerialDescriptor( * ``` * object LongAsStringSerializer : KSerializer<Long> { * override val descriptor: SerialDescriptor = - * PrimitiveDescriptor("kotlinx.serialization.LongAsStringSerializer", PrimitiveKind.STRING) + * PrimitiveSerialDescriptor("kotlinx.serialization.LongAsStringSerializer", PrimitiveKind.STRING) * * override fun serialize(encoder: Encoder, value: Long) { * encoder.encodeString(value.toString()) @@ -129,7 +132,7 @@ internal class WrappedSerialDescriptor(override val serialName: String, original * This function is left public only for migration of pre-release users and is not intended to be used * as generally-safe and stable mechanism. Beware that it can produce inconsistent or non spec-compliant instances. * - * If you end up using this builder, please file an issue with your use-case in kotlinx.serialization + * If you end up using this builder, please file an issue with your use-case in kotlinx.serialization issue tracker. */ @InternalSerializationApi @OptIn(ExperimentalSerializationApi::class) @@ -240,6 +243,7 @@ public class ClassSerialDescriptorBuilder internal constructor( * in its [KSerializer] type parameter and handle nulls during encoding and decoding. */ @ExperimentalSerializationApi + @Deprecated("isNullable inside buildSerialDescriptor is deprecated. Please use SerialDescriptor.nullable extension on a builder result.", level = DeprecationLevel.ERROR) public var isNullable: Boolean = false /** @@ -278,7 +282,7 @@ public class ClassSerialDescriptorBuilder internal constructor( annotations: List<Annotation> = emptyList(), isOptional: Boolean = false ) { - require(uniqueNames.add(elementName)) { "Element with name '$elementName' is already registered" } + require(uniqueNames.add(elementName)) { "Element with name '$elementName' is already registered in $serialName" } elementNames += elementName elementDescriptors += descriptor elementAnnotations += annotations diff --git a/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt b/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt index 8e2799c9..ffe6dd37 100644 --- a/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt +++ b/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt @@ -34,7 +34,7 @@ public abstract class AbstractDecoder : Decoder, CompositeDecoder { override fun decodeString(): String = decodeValue() as String override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeValue() as Int - override fun decodeInline(inlineDescriptor: SerialDescriptor): Decoder = this + override fun decodeInline(descriptor: SerialDescriptor): Decoder = this // overwrite by default public open fun <T : Any?> decodeSerializableValue( @@ -74,8 +74,7 @@ public abstract class AbstractDecoder : Decoder, CompositeDecoder { index: Int, deserializer: DeserializationStrategy<T?>, previousValue: T? - ): T? { - val isNullabilitySupported = deserializer.descriptor.isNullable - return if (isNullabilitySupported || decodeNotNullMark()) decodeSerializableValue(deserializer, previousValue) else decodeNull() + ): T? = decodeIfNullable(deserializer) { + decodeSerializableValue(deserializer, previousValue) } } diff --git a/core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt b/core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt index f197c40f..384cb8a0 100644 --- a/core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt +++ b/core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt @@ -51,7 +51,7 @@ public abstract class AbstractEncoder : Encoder, CompositeEncoder { override fun encodeString(value: String): Unit = encodeValue(value) override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int): Unit = encodeValue(index) - override fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder = this + override fun encodeInline(descriptor: SerialDescriptor): Encoder = this // Delegating implementation of CompositeEncoder final override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) { if (encodeElement(descriptor, index)) encodeBoolean(value) } diff --git a/core/commonMain/src/kotlinx/serialization/encoding/ChunkedDecoder.kt b/core/commonMain/src/kotlinx/serialization/encoding/ChunkedDecoder.kt new file mode 100644 index 00000000..016e07e2 --- /dev/null +++ b/core/commonMain/src/kotlinx/serialization/encoding/ChunkedDecoder.kt @@ -0,0 +1,51 @@ +package kotlinx.serialization.encoding + +import kotlinx.serialization.ExperimentalSerializationApi + +/** + * This interface indicates that decoder supports consuming large strings by chunks via consumeChunk method. + * Currently, only streaming json decoder implements this interface. + * Please note that this interface is only applicable to streaming decoders. That means that it is not possible to use + * some JsonTreeDecoder features like polymorphism with this interface. + */ +@ExperimentalSerializationApi +public interface ChunkedDecoder { + /** + * Method allows decoding a string value by fixed-size chunks. + * Usable for handling very large strings that may not fit in memory. + * Chunk size is guaranteed to not exceed 16384 chars (but it may be smaller than that). + * Feeds string chunks to the provided consumer. + * + * @param consumeChunk - lambda function to handle string chunks + * + * Example usage: + * ``` + * @Serializable(with = LargeStringSerializer::class) + * data class LargeStringData(val largeString: String) + * + * @Serializable + * data class ClassWithLargeStringDataField(val largeStringField: LargeStringData) + * + * object LargeStringSerializer : KSerializer<LargeStringData> { + * override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LargeStringContent", PrimitiveKind.STRING) + * + * override fun deserialize(decoder: Decoder): LargeStringData { + * require(decoder is ChunkedDecoder) { "Only chunked decoder supported" } + * + * val tmpFile = createTempFile() + * val writer = FileWriter(tmpFile.toFile()).use { + * decoder.decodeStringChunked { chunk -> + * writer.append(chunk) + * } + * } + * return LargeStringData("file://${tmpFile.absolutePathString()}") + * } + * } + * ``` + * + * In this sample, we need to be able to handle a huge string coming from json. Instead of storing it in memory, + * we offload it into a file and return the file name instead + */ + @ExperimentalSerializationApi + public fun decodeStringChunked(consumeChunk: (chunk: String) -> Unit) +}
\ No newline at end of file diff --git a/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt b/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt index 3e93e3db..dc4aa2ab 100644 --- a/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt +++ b/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt @@ -108,7 +108,7 @@ import kotlinx.serialization.modules.* * * ### Not stable for inheritance * - * `Decoder` interface is not stable for inheritance in 3rd party libraries, as new methods + * `Decoder` interface is not stable for inheritance in 3rd-party libraries, as new methods * might be added to this interface or contracts of the existing methods can be changed. */ public interface Decoder { @@ -211,27 +211,24 @@ public interface Decoder { public fun decodeEnum(enumDescriptor: SerialDescriptor): Int /** - * Returns [Decoder] for decoding an underlying type of an inline class. - * [inlineDescriptor] describes a target inline class. + * Returns [Decoder] for decoding an underlying type of a value class in an inline manner. + * [descriptor] describes a target value class. * - * Namely, for the `@Serializable inline class MyInt(val my: Int)`, - * the following sequence is used: + * Namely, for the `@Serializable @JvmInline value class MyInt(val my: Int)`, the following sequence is used: * ``` * thisDecoder.decodeInline(MyInt.serializer().descriptor).decodeInt() * ``` * - * Current decoder may return any other instance of [Decoder] class, - * depending on the provided [inlineDescriptor]. - * For example, when this function is called on Json decoder with - * `UInt.serializer().descriptor`, the returned decoder is able - * to decode unsigned integers. + * Current decoder may return any other instance of [Decoder] class, depending on the provided [descriptor]. + * For example, when this function is called on `Json` decoder with + * `UInt.serializer().descriptor`, the returned decoder is able to decode unsigned integers. * * Note that this function returns [Decoder] instead of the [CompositeDecoder] - * because inline classes always have the single property. - * Calling [Decoder.beginStructure] on returned instance leads to an undefined behavior. + * because value classes always have the single property. + * + * Calling [Decoder.beginStructure] on returned instance leads to an unspecified behavior and, in general, is prohibited. */ - @ExperimentalSerializationApi - public fun decodeInline(inlineDescriptor: SerialDescriptor): Decoder + public fun decodeInline(descriptor: SerialDescriptor): Decoder /** * Decodes the beginning of the nested structure in a serialized form @@ -263,12 +260,17 @@ public interface Decoder { * Decodes the nullable value of type [T] by delegating the decoding process to the given [deserializer]. */ @ExperimentalSerializationApi - public fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? { - val isNullabilitySupported = deserializer.descriptor.isNullable - return if (isNullabilitySupported || decodeNotNullMark()) decodeSerializableValue(deserializer) else decodeNull() + public fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? = decodeIfNullable(deserializer) { + decodeSerializableValue(deserializer) } } +@OptIn(ExperimentalSerializationApi::class) +internal inline fun <T : Any> Decoder.decodeIfNullable(deserializer: DeserializationStrategy<T?>, block: () -> T?): T? { + val isNullabilitySupported = deserializer.descriptor.isNullable + return if (isNullabilitySupported || decodeNotNullMark()) block() else decodeNull() +} + /** * [CompositeDecoder] is a part of decoding process that is bound to a particular structured part of * the serialized form, described by the serial descriptor passed to [Decoder.beginStructure]. @@ -488,35 +490,34 @@ public interface CompositeDecoder { public fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String /** - * Returns [Decoder] for decoding an underlying type of an inline class. - * Serializable inline class is described by the [child descriptor][SerialDescriptor.getElementDescriptor] + * Returns [Decoder] for decoding an underlying type of a value class in an inline manner. + * Serializable value class is described by the [child descriptor][SerialDescriptor.getElementDescriptor] * of given [descriptor] at [index]. * - * Namely, for the `@Serializable inline class MyInt(val my: Int)`, - * and `@Serializable class MyData(val myInt: MyInt)` - * the following sequence is used: + * Namely, for the `@Serializable @JvmInline value class MyInt(val my: Int)`, + * and `@Serializable class MyData(val myInt: MyInt)` the following sequence is used: * ``` * thisDecoder.decodeInlineElement(MyData.serializer().descriptor, 0).decodeInt() * ``` * - * This method provides an opportunity for the optimization and its invocation should be identical to + * This method provides an opportunity for the optimization to avoid boxing of a carried value + * and its invocation should be equivalent to the following: * ``` * thisDecoder.decodeSerializableElement(MyData.serializer.descriptor, 0, MyInt.serializer()) * ``` * * Current decoder may return any other instance of [Decoder] class, depending on the provided descriptor. - * For example, when this function is called on Json decoder with descriptor that has + * For example, when this function is called on `Json` decoder with descriptor that has * `UInt.serializer().descriptor` at the given [index], the returned decoder is able * to decode unsigned integers. * * Note that this function returns [Decoder] instead of the [CompositeDecoder] - * because inline classes always have the single property. - * Calling [Decoder.beginStructure] on returned instance leads to an undefined behavior. + * because value classes always have the single property. + * Calling [Decoder.beginStructure] on returned instance leads to an unspecified behavior and, in general, is prohibited. * * @see Decoder.decodeInline * @see SerialDescriptor.getElementDescriptor */ - @ExperimentalSerializationApi public fun decodeInlineElement( descriptor: SerialDescriptor, index: Int @@ -571,6 +572,3 @@ public inline fun <T> Decoder.decodeStructure( composite.endStructure(descriptor) return result } - -private const val decodeMethodDeprecated = "Please migrate to decodeElement method which accepts old value." + - "Feel free to ignore it if your format does not support updates." diff --git a/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt b/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt index 1113b1c7..2b1dd09c 100644 --- a/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt +++ b/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt @@ -207,27 +207,24 @@ public interface Encoder { public fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) /** - * Returns [Encoder] for encoding an underlying type of an inline class. - * [inlineDescriptor] describes a serializable inline class. + * Returns [Encoder] for encoding an underlying type of a value class in an inline manner. + * [descriptor] describes a serializable value class. * - * Namely, for the `@Serializable inline class MyInt(val my: Int)`, + * Namely, for the `@Serializable @JvmInline value class MyInt(val my: Int)`, * the following sequence is used: * ``` * thisEncoder.encodeInline(MyInt.serializer().descriptor).encodeInt(my) * ``` * - * Current encoder may return any other instance of [Encoder] class, - * depending on the provided [inlineDescriptor]. - * For example, when this function is called on Json encoder with - * `UInt.serializer().descriptor`, the returned encoder is able + * Current encoder may return any other instance of [Encoder] class, depending on the provided [descriptor]. + * For example, when this function is called on Json encoder with `UInt.serializer().descriptor`, the returned encoder is able * to encode unsigned integers. * - * Note that this function returns [Encoder] instead of [CompositeEncoder] - * because inline classes always have one property. - * Calling [Encoder.beginStructure] on returned instance leads to an undefined behavior. + * Note that this function returns [Encoder] instead of the [CompositeEncoder] + * because value classes always have the single property. + * Calling [Encoder.beginStructure] on returned instance leads to an unspecified behavior and, in general, is prohibited. */ - @ExperimentalSerializationApi - public fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder + public fun encodeInline(descriptor: SerialDescriptor): Encoder /** * Encodes the beginning of the nested structure in a serialized form @@ -411,36 +408,34 @@ public interface CompositeEncoder { public fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String) /** - * Returns [Encoder] for decoding an underlying type of an inline class. - * Serializable inline class is described by the [child descriptor][SerialDescriptor.getElementDescriptor] + * Returns [Encoder] for decoding an underlying type of a value class in an inline manner. + * Serializable value class is described by the [child descriptor][SerialDescriptor.getElementDescriptor] * of given [descriptor] at [index]. * - * Namely, for the `@Serializable inline class MyInt(val my: Int)`, - * and `@Serializable class MyData(val myInt: MyInt)` - * the following sequence is used: + * Namely, for the `@Serializable @JvmInline value class MyInt(val my: Int)`, + * and `@Serializable class MyData(val myInt: MyInt)` the following sequence is used: * ``` * thisEncoder.encodeInlineElement(MyData.serializer.descriptor, 0).encodeInt(my) * ``` * - * This method is an optimization and its invocation should have the exact same result as + * This method provides an opportunity for the optimization to avoid boxing of a carried value + * and its invocation should be equivalent to the following: * ``` * thisEncoder.encodeSerializableElement(MyData.serializer.descriptor, 0, MyInt.serializer(), myInt) * ``` * - * Current encoder may return any other instance of [Encoder] class, - * depending on provided descriptor. + * Current encoder may return any other instance of [Encoder] class, depending on provided descriptor. * For example, when this function is called on Json encoder with descriptor that has * `UInt.serializer().descriptor` at the given [index], the returned encoder is able * to encode unsigned integers. * - * Note that this function returns [Encoder] instead of [CompositeEncoder] - * because inline classes always have one property. - * Calling [Encoder.beginStructure] on returned instance leads to an undefined behavior. + * Note that this function returns [Encoder] instead of the [CompositeEncoder] + * because value classes always have the single property. + * Calling [Encoder.beginStructure] on returned instance leads to an unspecified behavior and, in general, is prohibited. * * @see Encoder.encodeInline * @see SerialDescriptor.getElementDescriptor */ - @ExperimentalSerializationApi public fun encodeInlineElement( descriptor: SerialDescriptor, index: Int diff --git a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt index a85b4659..26d3b5e2 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt @@ -58,8 +58,8 @@ public abstract class AbstractPolymorphicSerializer<T : Any> internal constructo } else -> throw SerializationException( "Invalid index in polymorphic deserialization of " + - (klassName ?: "unknown class") + - "\n Expected 0, 1 or DECODE_DONE(-1), but found $index" + (klassName ?: "unknown class") + + "\n Expected 0, 1 or DECODE_DONE(-1), but found $index" ) } } @@ -81,7 +81,7 @@ public abstract class AbstractPolymorphicSerializer<T : Any> internal constructo public open fun findPolymorphicSerializerOrNull( decoder: CompositeDecoder, klassName: String? - ): DeserializationStrategy<out T>? = decoder.serializersModule.getPolymorphic(baseClass, klassName) + ): DeserializationStrategy<T>? = decoder.serializersModule.getPolymorphic(baseClass, klassName) /** @@ -98,13 +98,14 @@ public abstract class AbstractPolymorphicSerializer<T : Any> internal constructo @JvmName("throwSubtypeNotRegistered") internal fun throwSubtypeNotRegistered(subClassName: String?, baseClass: KClass<*>): Nothing { - val scope = "in the scope of '${baseClass.simpleName}'" + val scope = "in the polymorphic scope of '${baseClass.simpleName}'" throw SerializationException( if (subClassName == null) - "Class discriminator was missing and no default polymorphic serializers were registered $scope" + "Class discriminator was missing and no default serializers were registered $scope." else - "Class '$subClassName' is not registered for polymorphic serialization $scope.\n" + - "Mark the base class as 'sealed' or register the serializer explicitly." + "Serializer for subclass '$subClassName' is not found $scope.\n" + + "Check if class with serial name '$subClassName' exists and serializer is registered in a corresponding SerializersModule.\n" + + "To be registered automatically, class '$subClassName' has to be '@Serializable', and the base class '${baseClass.simpleName}' has to be sealed and '@Serializable'." ) } diff --git a/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt b/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt new file mode 100644 index 00000000..2e64a770 --- /dev/null +++ b/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt @@ -0,0 +1,39 @@ +/* + * 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 kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.time.Duration + + +@PublishedApi +internal object DurationSerializer : KSerializer<Duration> { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlin.time.Duration", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Duration) { + encoder.encodeString(value.toIsoString()) + } + + override fun deserialize(decoder: Decoder): Duration { + return Duration.parseIsoString(decoder.decodeString()) + } +} + +@PublishedApi +internal object NothingSerializer : KSerializer<Nothing> { + override val descriptor: SerialDescriptor = NothingSerialDescriptor + + override fun serialize(encoder: Encoder, value: Nothing) { + throw SerializationException("'kotlin.Nothing' cannot be serialized") + } + + override fun deserialize(decoder: Decoder): Nothing { + throw SerializationException("'kotlin.Nothing' does not have instances") + } +} diff --git a/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt b/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt index fca90262..5e6736d3 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt @@ -4,13 +4,13 @@ package kotlinx.serialization.internal -import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.* import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.CompositeDecoder @OptIn(ExperimentalSerializationApi::class) -@PublishedApi -internal class ElementMarker( +@CoreFriendModuleApi +public class ElementMarker( private val descriptor: SerialDescriptor, // Instead of inheritance and virtual function in order to keep cross-module internal modifier via suppresses // Can be reworked via public + internal api if necessary @@ -45,7 +45,7 @@ internal class ElementMarker( } } - fun mark(index: Int) { + public fun mark(index: Int) { if (index < Long.SIZE_BITS) { lowerMarks = lowerMarks or (1L shl index) } else { @@ -53,7 +53,7 @@ internal class ElementMarker( } } - fun nextUnmarkedIndex(): Int { + public fun nextUnmarkedIndex(): Int { val elementsCount = descriptor.elementsCount while (lowerMarks != -1L) { val index = lowerMarks.inv().countTrailingZeroBits() diff --git a/core/commonMain/src/kotlinx/serialization/internal/Enums.kt b/core/commonMain/src/kotlinx/serialization/internal/Enums.kt index 1e620154..90800d7b 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/Enums.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/Enums.kt @@ -49,20 +49,79 @@ internal class EnumDescriptor( } } -// Used for enums that are not explicitly serializable by the plugin +@OptIn(ExperimentalSerializationApi::class) +@InternalSerializationApi +internal fun <T : Enum<T>> createSimpleEnumSerializer(serialName: String, values: Array<T>): KSerializer<T> { + return EnumSerializer(serialName, values) +} + +/** + * The function has a bug (#2121) and should not be used by new (1.8.20+) plugins. It is preserved for backward compatibility with previously compiled enum classes. + */ +@OptIn(ExperimentalSerializationApi::class) +@InternalSerializationApi +internal fun <T : Enum<T>> createMarkedEnumSerializer( + serialName: String, + values: Array<T>, + names: Array<String?>, + annotations: Array<Array<Annotation>?> +): KSerializer<T> { + val descriptor = EnumDescriptor(serialName, values.size) + values.forEachIndexed { i, v -> + val elementName = names.getOrNull(i) ?: v.name + descriptor.addElement(elementName) + annotations.getOrNull(i)?.forEach { + descriptor.pushAnnotation(it) + } + } + + return EnumSerializer(serialName, values, descriptor) +} + +@OptIn(ExperimentalSerializationApi::class) +@InternalSerializationApi +internal fun <T : Enum<T>> createAnnotatedEnumSerializer( + serialName: String, + values: Array<T>, + names: Array<String?>, + entryAnnotations: Array<Array<Annotation>?>, + classAnnotations: Array<Annotation>? +): KSerializer<T> { + val descriptor = EnumDescriptor(serialName, values.size) + classAnnotations?.forEach { + descriptor.pushClassAnnotation(it) + } + values.forEachIndexed { i, v -> + val elementName = names.getOrNull(i) ?: v.name + descriptor.addElement(elementName) + entryAnnotations.getOrNull(i)?.forEach { + descriptor.pushAnnotation(it) + } + } + + return EnumSerializer(serialName, values, descriptor) +} + @PublishedApi @OptIn(ExperimentalSerializationApi::class) internal class EnumSerializer<T : Enum<T>>( serialName: String, private val values: Array<T> ) : KSerializer<T> { + private var overriddenDescriptor: SerialDescriptor? = null - override val descriptor: SerialDescriptor = buildSerialDescriptor(serialName, SerialKind.ENUM) { - values.forEach { - val fqn = "$serialName.${it.name}" - val enumMemberDescriptor = buildSerialDescriptor(fqn, StructureKind.OBJECT) - element(it.name, enumMemberDescriptor) - } + internal constructor(serialName: String, values: Array<T>, descriptor: SerialDescriptor) : this(serialName, values) { + overriddenDescriptor = descriptor + } + + override val descriptor: SerialDescriptor by lazy { + overriddenDescriptor ?: createUnmarkedDescriptor(serialName) + } + + private fun createUnmarkedDescriptor(serialName: String): SerialDescriptor { + val d = EnumDescriptor(serialName, values.size) + values.forEach { d.addElement(it.name) } + return d } override fun serialize(encoder: Encoder, value: T) { diff --git a/core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt index 05fd92be..ec9edc96 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt @@ -10,7 +10,6 @@ import kotlinx.serialization.encoding.* @Suppress("Unused") @PublishedApi -@OptIn(ExperimentalSerializationApi::class) internal class InlineClassDescriptor( name: String, generatedSerializer: GeneratedSerializer<*> @@ -26,7 +25,8 @@ internal class InlineClassDescriptor( } } -internal fun <T> InlinePrimitiveDescriptor(name: String, primitiveSerializer: KSerializer<T>): SerialDescriptor = +@InternalSerializationApi +public fun <T> InlinePrimitiveDescriptor(name: String, primitiveSerializer: KSerializer<T>): SerialDescriptor = InlineClassDescriptor(name, object : GeneratedSerializer<T> { // object needed only to pass childSerializers() override fun childSerializers(): Array<KSerializer<*>> = arrayOf(primitiveSerializer) diff --git a/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt b/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt index d79cb8b9..e733827f 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt @@ -1,14 +1,14 @@ package kotlinx.serialization.internal -import kotlinx.serialization.* import kotlinx.serialization.descriptors.* /* - * Methods that are required for kotlinx-serialization-json, but are not effectively public - * and actually represent our own technical debt. - * This methods are not intended for public use + * Methods that are required for kotlinx-serialization-json, but are not effectively public. + * + * Anything marker with this annotation is not intended for public use. */ +@RequiresOptIn(level = RequiresOptIn.Level.ERROR) +internal annotation class CoreFriendModuleApi -@InternalSerializationApi -@Deprecated(message = "Should not be used", level = DeprecationLevel.ERROR) +@CoreFriendModuleApi public fun SerialDescriptor.jsonCachedSerialNames(): Set<String> = cachedSerialNames() diff --git a/core/commonMain/src/kotlinx/serialization/internal/NamedCompanion.kt b/core/commonMain/src/kotlinx/serialization/internal/NamedCompanion.kt new file mode 100644 index 00000000..0756dc62 --- /dev/null +++ b/core/commonMain/src/kotlinx/serialization/internal/NamedCompanion.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.internal + +import kotlinx.serialization.* + +/** + * An annotation added by the compiler to the companion object of [Serializable] class, if it has a non-default name. + */ +@InternalSerializationApi +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +public annotation class NamedCompanion diff --git a/core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt b/core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt index 463e4863..ce366bdc 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt @@ -14,7 +14,7 @@ import kotlinx.serialization.modules.* */ @OptIn(ExperimentalSerializationApi::class) internal object NoOpEncoder : AbstractEncoder() { - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() public override fun encodeValue(value: Any): Unit = Unit diff --git a/core/commonMain/src/kotlinx/serialization/internal/NothingSerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/NothingSerialDescriptor.kt new file mode 100644 index 00000000..aab492a0 --- /dev/null +++ b/core/commonMain/src/kotlinx/serialization/internal/NothingSerialDescriptor.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:OptIn(ExperimentalSerializationApi::class) + +package kotlinx.serialization.internal + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.StructureKind + +internal object NothingSerialDescriptor : SerialDescriptor { + public override val kind: SerialKind = StructureKind.OBJECT + + public override val serialName: String = "kotlin.Nothing" + + override val elementsCount: Int get() = 0 + override fun getElementName(index: Int): String = error() + override fun getElementIndex(name: String): Int = error() + override fun isElementOptional(index: Int): Boolean = error() + override fun getElementDescriptor(index: Int): SerialDescriptor = error() + override fun getElementAnnotations(index: Int): List<Annotation> = error() + override fun toString(): String = "NothingSerialDescriptor" + override fun equals(other: Any?): Boolean { + return this === other + } + + override fun hashCode(): Int = serialName.hashCode() + 31 * kind.hashCode() + private fun error(): Nothing = + throw IllegalStateException("Descriptor for type `kotlin.Nothing` does not have elements") +} diff --git a/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt index ebb9e2e4..ac9ee8e3 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt @@ -41,6 +41,9 @@ internal class ObjectSerializer<T : Any>(serialName: String, private val objectI override fun deserialize(decoder: Decoder): T { decoder.decodeStructure(descriptor) { + if (decodeSequentially()) + return@decodeStructure + when (val index = decodeElementIndex(descriptor)) { CompositeDecoder.DECODE_DONE -> { return@decodeStructure diff --git a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt index fde2c36a..ef313ccd 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt @@ -21,7 +21,7 @@ internal object InternalHexConverter { while (i < len) { val h = hexToInt(s[i]) val l = hexToInt(s[i + 1]) - require(!(h == -1 || l == -1)) { "Invalid hex chars: ${s[i]}${s[i+1]}" } + require(!(h == -1 || l == -1)) { "Invalid hex chars: ${s[i]}${s[i + 1]}" } bytes[i / 2] = ((h shl 4) + l).toByte() i += 2 @@ -65,7 +65,6 @@ internal fun SerialDescriptor.cachedSerialNames(): Set<String> { return result } -@SharedImmutable private val EMPTY_DESCRIPTOR_ARRAY: Array<SerialDescriptor> = arrayOf() /** @@ -85,28 +84,40 @@ internal inline fun <T> SerializationStrategy<*>.cast(): SerializationStrategy<T @Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE") @PublishedApi -internal inline fun <T> DeserializationStrategy<*>.cast(): DeserializationStrategy<T> = this as DeserializationStrategy<T> +internal inline fun <T> DeserializationStrategy<*>.cast(): DeserializationStrategy<T> = + this as DeserializationStrategy<T> internal fun KClass<*>.serializerNotRegistered(): Nothing { - throw SerializationException( - "Serializer for class '${simpleName}' is not found.\n" + - "Mark the class as @Serializable or provide the serializer explicitly." - ) + throw SerializationException(notRegisteredMessage()) } +internal fun KClass<*>.notRegisteredMessage(): String = notRegisteredMessage(simpleName ?: "<local class name not available>") + +internal fun notRegisteredMessage(className: String): String = "Serializer for class '$className' is not found.\n" + + "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.\n" + internal expect fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing @Suppress("UNCHECKED_CAST") internal fun KType.kclass() = when (val t = classifier) { is KClass<*> -> t is KTypeParameter -> { - error("Captured type paramerer $t from generic non-reified function. " + - "Such functionality cannot be supported as $t is erased, either specify serializer explicitly or make " + - "calling function inline with reified $t") + // If you are going to change this error message, please also actualize the message in the compiler intrinsics here: + // Kotlin/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializationJvmIrIntrinsicSupport.kt#argumentTypeOrGenerateException + throw IllegalArgumentException( + "Captured type parameter $t from generic non-reified function. " + + "Such functionality cannot be supported because $t is erased, either specify serializer explicitly or make " + + "calling function inline with reified $t." + ) } - else -> error("Only KClass supported as classifier, got $t") + + else -> throw IllegalArgumentException("Only KClass supported as classifier, got $t") } as KClass<Any> +// If you are going to change this error message, please also actualize the message in the compiler intrinsics here: +// Kotlin/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializationJvmIrIntrinsicSupport.kt#argumentTypeOrGenerateException +internal fun KTypeProjection.typeOrThrow(): KType = requireNotNull(type) { "Star projections in type arguments are not allowed, but had $type" } + /** * Constructs KSerializer<D<T0, T1, ...>> by given KSerializer<T0>, KSerializer<T1>, ... * via reflection (on JVM) or compiler+plugin intrinsic `SerializerFactory` (on Native) @@ -130,18 +141,41 @@ internal expect fun BooleanArray.getChecked(index: Int): Boolean internal expect fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? -internal expect fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> +/** + * 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> /** - * Checks whether the receiver is an instance of a given kclass. - * - * This check is a replacement for [KClass.isInstance] because on JVM it requires kotlin-reflect.jar in classpath (see KT-14720). - * - * On JS and Native, this function delegates to aforementioned [KClass.isInstance] since it is supported there out-of-the-box; - * on JVM, it falls back to `java.lang.Class.isInstance`, which causes difference when applied to function types with big arity. + * 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 Any.isInstanceOf(kclass: KClass<*>): Boolean +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> 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/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt index 7b6efe7e..a954bdab 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt @@ -1,7 +1,7 @@ /* * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE", "UNUSED") +@file:Suppress("UNUSED") package kotlinx.serialization.internal diff --git a/core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt b/core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt index f613c2ad..61272d29 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt @@ -8,7 +8,6 @@ import kotlinx.serialization.* import kotlin.jvm.* import kotlin.native.concurrent.* -@SharedImmutable @JvmField internal val EMPTY_SERIALIZER_ARRAY: Array<KSerializer<*>> = arrayOf() diff --git a/core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt b/core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt index 7427b160..1e6172f0 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt @@ -408,3 +408,223 @@ internal class BooleanArrayBuilder internal constructor( override fun build() = buffer.copyOf(position) } + + +// Unsigned arrays + +/** + * Serializer for [UByteArray]. + * + * Encode elements one-by-one, as regular list, + * unless format's Encoder/Decoder have special handling for this serializer. + */ +@PublishedApi +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +internal object UByteArraySerializer : KSerializer<UByteArray>, + PrimitiveArraySerializer<UByte, UByteArray, UByteArrayBuilder>(UByte.serializer()) { + + override fun UByteArray.collectionSize(): Int = size + override fun UByteArray.toBuilder(): UByteArrayBuilder = UByteArrayBuilder(this) + override fun empty(): UByteArray = UByteArray(0) + + override fun readElement(decoder: CompositeDecoder, index: Int, builder: UByteArrayBuilder, checkIndex: Boolean) { + builder.append(decoder.decodeInlineElement(descriptor, index).decodeByte().toUByte()) + } + + override fun writeContent(encoder: CompositeEncoder, content: UByteArray, size: Int) { + for (i in 0 until size) + encoder.encodeInlineElement(descriptor, i).encodeByte(content[i].toByte()) + } +} + +@PublishedApi +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +internal class UByteArrayBuilder internal constructor( + bufferWithData: UByteArray +) : PrimitiveArrayBuilder<UByteArray>() { + + private var buffer: UByteArray = bufferWithData + override var position: Int = bufferWithData.size + private set + + init { + ensureCapacity(INITIAL_SIZE) + } + + override fun ensureCapacity(requiredCapacity: Int) { + if (buffer.size < requiredCapacity) + buffer = buffer.copyOf(requiredCapacity.coerceAtLeast(buffer.size * 2)) + } + + internal fun append(c: UByte) { + ensureCapacity() + buffer[position++] = c + } + + override fun build() = buffer.copyOf(position) +} + +/** + * Serializer for [UShortArray]. + * + * Encode elements one-by-one, as regular list, + * unless format's Encoder/Decoder have special handling for this serializer. + */ +@PublishedApi +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +internal object UShortArraySerializer : KSerializer<UShortArray>, + PrimitiveArraySerializer<UShort, UShortArray, UShortArrayBuilder>(UShort.serializer()) { + + override fun UShortArray.collectionSize(): Int = size + override fun UShortArray.toBuilder(): UShortArrayBuilder = UShortArrayBuilder(this) + override fun empty(): UShortArray = UShortArray(0) + + override fun readElement(decoder: CompositeDecoder, index: Int, builder: UShortArrayBuilder, checkIndex: Boolean) { + builder.append(decoder.decodeInlineElement(descriptor, index).decodeShort().toUShort()) + } + + override fun writeContent(encoder: CompositeEncoder, content: UShortArray, size: Int) { + for (i in 0 until size) + encoder.encodeInlineElement(descriptor, i).encodeShort(content[i].toShort()) + } +} + +@PublishedApi +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +internal class UShortArrayBuilder internal constructor( + bufferWithData: UShortArray +) : PrimitiveArrayBuilder<UShortArray>() { + + private var buffer: UShortArray = bufferWithData + override var position: Int = bufferWithData.size + private set + + init { + ensureCapacity(INITIAL_SIZE) + } + + override fun ensureCapacity(requiredCapacity: Int) { + if (buffer.size < requiredCapacity) + buffer = buffer.copyOf(requiredCapacity.coerceAtLeast(buffer.size * 2)) + } + + internal fun append(c: UShort) { + ensureCapacity() + buffer[position++] = c + } + + override fun build() = buffer.copyOf(position) +} + +/** + * Serializer for [UIntArray]. + * + * Encode elements one-by-one, as regular list, + * unless format's Encoder/Decoder have special handling for this serializer. + */ +@PublishedApi +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +internal object UIntArraySerializer : KSerializer<UIntArray>, + PrimitiveArraySerializer<UInt, UIntArray, UIntArrayBuilder>(UInt.serializer()) { + + override fun UIntArray.collectionSize(): Int = size + override fun UIntArray.toBuilder(): UIntArrayBuilder = UIntArrayBuilder(this) + override fun empty(): UIntArray = UIntArray(0) + + override fun readElement(decoder: CompositeDecoder, index: Int, builder: UIntArrayBuilder, checkIndex: Boolean) { + builder.append(decoder.decodeInlineElement(descriptor, index).decodeInt().toUInt()) + } + + override fun writeContent(encoder: CompositeEncoder, content: UIntArray, size: Int) { + for (i in 0 until size) + encoder.encodeInlineElement(descriptor, i).encodeInt(content[i].toInt()) + } +} + +@PublishedApi +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +internal class UIntArrayBuilder internal constructor( + bufferWithData: UIntArray +) : PrimitiveArrayBuilder<UIntArray>() { + + private var buffer: UIntArray = bufferWithData + override var position: Int = bufferWithData.size + private set + + init { + ensureCapacity(INITIAL_SIZE) + } + + override fun ensureCapacity(requiredCapacity: Int) { + if (buffer.size < requiredCapacity) + buffer = buffer.copyOf(requiredCapacity.coerceAtLeast(buffer.size * 2)) + } + + internal fun append(c: UInt) { + ensureCapacity() + buffer[position++] = c + } + + override fun build() = buffer.copyOf(position) +} + +/** + * Serializer for [ULongArray]. + * + * Encode elements one-by-one, as regular list, + * unless format's Encoder/Decoder have special handling for this serializer. + */ +@PublishedApi +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +internal object ULongArraySerializer : KSerializer<ULongArray>, + PrimitiveArraySerializer<ULong, ULongArray, ULongArrayBuilder>(ULong.serializer()) { + + override fun ULongArray.collectionSize(): Int = size + override fun ULongArray.toBuilder(): ULongArrayBuilder = ULongArrayBuilder(this) + override fun empty(): ULongArray = ULongArray(0) + + override fun readElement(decoder: CompositeDecoder, index: Int, builder: ULongArrayBuilder, checkIndex: Boolean) { + builder.append(decoder.decodeInlineElement(descriptor, index).decodeLong().toULong()) + } + + override fun writeContent(encoder: CompositeEncoder, content: ULongArray, size: Int) { + for (i in 0 until size) + encoder.encodeInlineElement(descriptor, i).encodeLong(content[i].toLong()) + } +} + +@PublishedApi +@ExperimentalSerializationApi +@ExperimentalUnsignedTypes +internal class ULongArrayBuilder internal constructor( + bufferWithData: ULongArray +) : PrimitiveArrayBuilder<ULongArray>() { + + private var buffer: ULongArray = bufferWithData + override var position: Int = bufferWithData.size + private set + + init { + ensureCapacity(INITIAL_SIZE) + } + + override fun ensureCapacity(requiredCapacity: Int) { + if (buffer.size < requiredCapacity) + buffer = buffer.copyOf(requiredCapacity.coerceAtLeast(buffer.size * 2)) + } + + internal fun append(c: ULong) { + ensureCapacity() + buffer[position++] = c + } + + override fun build() = buffer.copyOf(position) +} + diff --git a/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt b/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt index ab127ffa..2d9c5285 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt @@ -13,8 +13,9 @@ import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* import kotlin.native.concurrent.* import kotlin.reflect.* +import kotlin.time.Duration -@SharedImmutable +@OptIn(ExperimentalUnsignedTypes::class) private val BUILTIN_SERIALIZERS = mapOf( String::class to String.serializer(), Char::class to Char.serializer(), @@ -25,15 +26,25 @@ private val BUILTIN_SERIALIZERS = mapOf( FloatArray::class to FloatArraySerializer(), Long::class to Long.serializer(), LongArray::class to LongArraySerializer(), + ULong::class to ULong.serializer(), + ULongArray::class to ULongArraySerializer(), Int::class to Int.serializer(), IntArray::class to IntArraySerializer(), + UInt::class to UInt.serializer(), + UIntArray::class to UIntArraySerializer(), Short::class to Short.serializer(), ShortArray::class to ShortArraySerializer(), + UShort::class to UShort.serializer(), + UShortArray::class to UShortArraySerializer(), Byte::class to Byte.serializer(), ByteArray::class to ByteArraySerializer(), + UByte::class to UByte.serializer(), + UByteArray::class to UByteArraySerializer(), Boolean::class to Boolean.serializer(), BooleanArray::class to BooleanArraySerializer(), - Unit::class to Unit.serializer() + Unit::class to Unit.serializer(), + Nothing::class to NothingSerializer(), + Duration::class to Duration.serializer() ) internal class PrimitiveSerialDescriptor( @@ -47,6 +58,13 @@ internal class PrimitiveSerialDescriptor( override fun getElementDescriptor(index: Int): SerialDescriptor = error() override fun getElementAnnotations(index: Int): List<Annotation> = error() override fun toString(): String = "PrimitiveDescriptor($serialName)" + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PrimitiveSerialDescriptor) return false + if (serialName == other.serialName && kind == other.kind) return true + return false + } + override fun hashCode() = serialName.hashCode() + 31 * kind.hashCode() private fun error(): Nothing = throw IllegalStateException("Primitive descriptor does not have elements") } diff --git a/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt b/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt index e2e8d8f9..cf71388c 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt @@ -24,12 +24,13 @@ public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder { protected abstract fun SerialDescriptor.getTag(index: Int): Tag override val serializersModule: SerializersModule - get() = EmptySerializersModule + get() = EmptySerializersModule() // ---- API ---- protected open fun encodeTaggedValue(tag: Tag, value: Any): Unit = throw SerializationException("Non-serializable ${value::class} is not supported by ${this::class} encoder") + protected open fun encodeTaggedNonNullMark(tag: Tag) {} protected open fun encodeTaggedNull(tag: Tag): Unit = throw SerializationException("null is not supported") protected open fun encodeTaggedInt(tag: Tag, value: Int): Unit = encodeTaggedValue(tag, value) protected open fun encodeTaggedByte(tag: Tag, value: Byte): Unit = encodeTaggedValue(tag, value) @@ -50,8 +51,8 @@ public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder { protected open fun encodeTaggedInline(tag: Tag, inlineDescriptor: SerialDescriptor): Encoder = this.apply { pushTag(tag) } - final override fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder = - encodeTaggedInline(popTag(), inlineDescriptor) + override fun encodeInline(descriptor: SerialDescriptor): Encoder = + encodeTaggedInline(popTag(), descriptor) // ---- Implementation of low-level API ---- @@ -61,7 +62,7 @@ public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder { return true } - final override fun encodeNotNullMark() {} // Does nothing, open because is not really required + open override fun encodeNotNullMark(): Unit = encodeTaggedNonNullMark(currentTag) open override fun encodeNull(): Unit = encodeTaggedNull(popTag()) final override fun encodeBoolean(value: Boolean): Unit = encodeTaggedBoolean(popTag(), value) final override fun encodeByte(value: Byte): Unit = encodeTaggedByte(popTag(), value) @@ -177,7 +178,7 @@ public abstract class NamedValueEncoder : TaggedEncoder<String>() { @InternalSerializationApi public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder { override val serializersModule: SerializersModule - get() = EmptySerializersModule + get() = EmptySerializersModule() protected abstract fun SerialDescriptor.getTag(index: Int): Tag @@ -205,11 +206,10 @@ public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder { protected open fun <T : Any?> decodeSerializableValue(deserializer: DeserializationStrategy<T>, previousValue: T?): T = decodeSerializableValue(deserializer) - // ---- Implementation of low-level API ---- - final override fun decodeInline(inlineDescriptor: SerialDescriptor): Decoder = - decodeTaggedInline(popTag(), inlineDescriptor) + override fun decodeInline(descriptor: SerialDescriptor): Decoder = + decodeTaggedInline(popTag(), descriptor) // TODO this method should be overridden by any sane format that supports top-level nulls override fun decodeNotNullMark(): Boolean { @@ -283,13 +283,11 @@ public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder { index: Int, deserializer: DeserializationStrategy<T?>, previousValue: T? - ): T? = - tagBlock(descriptor.getTag(index)) { - if (decodeNotNullMark()) decodeSerializableValue( - deserializer, - previousValue - ) else decodeNull() + ): T? = tagBlock(descriptor.getTag(index)) { + decodeIfNullable(deserializer) { + decodeSerializableValue(deserializer, previousValue) } + } private fun <E> tagBlock(tag: Tag, block: () -> E): E { pushTag(tag) @@ -330,7 +328,7 @@ public abstract class NamedValueDecoder : TaggedDecoder<String>() { final override fun SerialDescriptor.getTag(index: Int): String = nested(elementName(this, index)) protected fun nested(nestedName: String): String = composeName(currentTagOrNull ?: "", nestedName) - protected open fun elementName(desc: SerialDescriptor, index: Int): String = desc.getElementName(index) + protected open fun elementName(descriptor: SerialDescriptor, index: Int): String = descriptor.getElementName(index) protected open fun composeName(parentName: String, childName: String): String = if (parentName.isEmpty()) childName else "$parentName.$childName" } diff --git a/core/commonMain/src/kotlinx/serialization/internal/Tuples.kt b/core/commonMain/src/kotlinx/serialization/internal/Tuples.kt index 1d30047e..1bd21cbe 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/Tuples.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/Tuples.kt @@ -11,7 +11,6 @@ import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* import kotlin.native.concurrent.* -@SharedImmutable private val NULL = Any() private const val deprecationMessage = "This class is used only by the plugin in generated code and should not be used directly. Use corresponding factory functions instead" @@ -33,35 +32,33 @@ internal sealed class KeyValueSerializer<K, V, R>( structuredEncoder.endStructure(descriptor) } - override fun deserialize(decoder: Decoder): R { - val composite = decoder.beginStructure(descriptor) - if (composite.decodeSequentially()) { - val key = composite.decodeSerializableElement(descriptor, 0, keySerializer) - val value = composite.decodeSerializableElement(descriptor, 1, valueSerializer) - return toResult(key, value) + override fun deserialize(decoder: Decoder): R = decoder.decodeStructure(descriptor) { + if (decodeSequentially()) { + val key = decodeSerializableElement(descriptor, 0, keySerializer) + val value = decodeSerializableElement(descriptor, 1, valueSerializer) + return@decodeStructure toResult(key, value) } var key: Any? = NULL var value: Any? = NULL mainLoop@ while (true) { - when (val idx = composite.decodeElementIndex(descriptor)) { + when (val idx = decodeElementIndex(descriptor)) { CompositeDecoder.DECODE_DONE -> { break@mainLoop } 0 -> { - key = composite.decodeSerializableElement(descriptor, 0, keySerializer) + key = decodeSerializableElement(descriptor, 0, keySerializer) } 1 -> { - value = composite.decodeSerializableElement(descriptor, 1, valueSerializer) + value = decodeSerializableElement(descriptor, 1, valueSerializer) } else -> throw SerializationException("Invalid index: $idx") } } - composite.endStructure(descriptor) if (key === NULL) throw SerializationException("Element 'key' is missing") if (value === NULL) throw SerializationException("Element 'value' is missing") @Suppress("UNCHECKED_CAST") - return toResult(key as K, value as V) + return@decodeStructure toResult(key as K, value as V) } } diff --git a/core/commonMain/src/kotlinx/serialization/internal/InlineClasses.kt b/core/commonMain/src/kotlinx/serialization/internal/ValueClasses.kt index b9738908..90c1f28d 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/InlineClasses.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/ValueClasses.kt @@ -10,8 +10,6 @@ import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* @PublishedApi -@ExperimentalSerializationApi -@ExperimentalUnsignedTypes internal object UIntSerializer : KSerializer<UInt> { override val descriptor: SerialDescriptor = InlinePrimitiveDescriptor("kotlin.UInt", Int.serializer()) @@ -25,8 +23,6 @@ internal object UIntSerializer : KSerializer<UInt> { } @PublishedApi -@ExperimentalSerializationApi -@ExperimentalUnsignedTypes internal object ULongSerializer : KSerializer<ULong> { override val descriptor: SerialDescriptor = InlinePrimitiveDescriptor("kotlin.ULong", Long.serializer()) @@ -40,8 +36,6 @@ internal object ULongSerializer : KSerializer<ULong> { } @PublishedApi -@ExperimentalSerializationApi -@ExperimentalUnsignedTypes internal object UByteSerializer : KSerializer<UByte> { override val descriptor: SerialDescriptor = InlinePrimitiveDescriptor("kotlin.UByte", Byte.serializer()) @@ -55,8 +49,6 @@ internal object UByteSerializer : KSerializer<UByte> { } @PublishedApi -@ExperimentalSerializationApi -@ExperimentalUnsignedTypes internal object UShortSerializer : KSerializer<UShort> { override val descriptor: SerialDescriptor = InlinePrimitiveDescriptor("kotlin.UShort", Short.serializer()) diff --git a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt index 31ce4574..1b8d431e 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt @@ -21,7 +21,7 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons ) { private val subclasses: MutableList<Pair<KClass<out Base>, KSerializer<out Base>>> = mutableListOf() private var defaultSerializerProvider: ((Base) -> SerializationStrategy<Base>?)? = null - private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy<out Base>?)? = null + private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy<Base>?)? = null /** * Registers a [subclass] [serializer] in the resulting module under the [base class][Base]. @@ -34,19 +34,20 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons * Adds a default serializers provider associated with the given [baseClass] to the resulting module. * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className` * were found. `className` could be `null` for formats that support nullable class discriminators - * (currently only [Json] with [useArrayPolymorphism][JsonBuilder.useArrayPolymorphism] set to `false`) + * (currently only `Json` with `JsonBuilder.useArrayPolymorphism` set to `false`) + * + * Default deserializers provider affects only deserialization process. To affect serialization process, use + * [SerializersModuleBuilder.polymorphicDefaultSerializer]. * * [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically. * * Typically, if the class is not registered in advance, it is not possible to know the structure of the unknown * type and have a precise serializer, so the default serializer has limited capabilities. - * To have a structural access to the unknown data, it is recommended to use [JsonTransformingSerializer] - * or [JsonContentPolymorphicSerializer] classes. + * If you're using `Json` format, you can get a structural access to the unknown data using `JsonContentPolymorphicSerializer`. * - * Default deserializers provider affects only deserialization process. + * @see SerializersModuleBuilder.polymorphicDefaultSerializer */ - @ExperimentalSerializationApi - public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?) { + public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?) { require(this.defaultDeserializerProvider == null) { "Default deserializer provider is already registered for class $baseClass: ${this.defaultDeserializerProvider}" } @@ -55,27 +56,28 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons /** * Adds a default deserializers provider associated with the given [baseClass] to the resulting module. + * This function affect only deserialization process. To avoid confusion, it was deprecated and replaced with [defaultDeserializer]. + * To affect serialization process, use [SerializersModuleBuilder.polymorphicDefaultSerializer]. + * * [defaultSerializerProvider] is invoked when no polymorphic serializers associated with the `className` * were found. `className` could be `null` for formats that support nullable class discriminators - * (currently only [Json] with [useArrayPolymorphism][JsonBuilder.useArrayPolymorphism] set to `false`) + * (currently only `Json` with `JsonBuilder.useArrayPolymorphism` set to `false`) * * [defaultSerializerProvider] can be stateful and lookup a serializer for the missing type dynamically. * - * [defaultSerializerProvider] is named as such for backwards compatibility reasons; it provides deserializers. - * * Typically, if the class is not registered in advance, it is not possible to know the structure of the unknown * type and have a precise serializer, so the default serializer has limited capabilities. - * To have a structural access to the unknown data, it is recommended to use [JsonTransformingSerializer] - * or [JsonContentPolymorphicSerializer] classes. - * - * Default deserializers provider affects only deserialization process. To affect serialization process, use - * [SerializersModuleBuilder.polymorphicDefaultSerializer]. + * If you're using `Json` format, you can get a structural access to the unknown data using `JsonContentPolymorphicSerializer`. * * @see defaultDeserializer + * @see SerializersModuleBuilder.polymorphicDefaultSerializer */ - @OptIn(ExperimentalSerializationApi::class) - // TODO: deprecate in 1.4 - public fun default(defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?) { + @Deprecated( + "Deprecated in favor of function with more precise name: defaultDeserializer", + ReplaceWith("defaultDeserializer(defaultSerializerProvider)"), + DeprecationLevel.WARNING // Since 1.5.0. Raise to ERROR in 1.6.0, hide in 1.7.0 + ) + public fun default(defaultSerializerProvider: (className: String?) -> DeserializationStrategy<Base>?) { defaultDeserializer(defaultSerializerProvider) } diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt index 86f66d7a..8a9126d7 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt @@ -6,8 +6,8 @@ package kotlinx.serialization.modules import kotlinx.serialization.* import kotlinx.serialization.internal.* +import kotlin.js.* import kotlin.jvm.* -import kotlin.native.concurrent.* import kotlin.reflect.* /** @@ -18,6 +18,9 @@ import kotlin.reflect.* * To enable runtime serializers resolution, one of the special annotations must be used on target types * ([Polymorphic] or [Contextual]), and a serial module with serializers should be used during construction of [SerialFormat]. * + * Serializers module can be built with `SerializersModule {}` builder function. + * Empty module can be obtained with `EmptySerializersModule()` factory function. + * * @see Contextual * @see Polymorphic */ @@ -28,7 +31,7 @@ public sealed class SerializersModule { "Deprecated in favor of overload with default parameter", ReplaceWith("getContextual(kclass)"), DeprecationLevel.HIDDEN - ) // Was stable since 1.0.0, HIDDEN in 1.2.0 in a backwards-compatible manner + ) // Was experimental since 1.0.0, HIDDEN in 1.2.0 in a backwards-compatible manner public fun <T : Any> getContextual(kclass: KClass<T>): KSerializer<T>? = getContextual(kclass, emptyList()) @@ -57,7 +60,7 @@ public sealed class SerializersModule { * or default value constructed from [serializedClassName] if a default serializer provider was registered. */ @ExperimentalSerializationApi - public abstract fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<out T>? + public abstract fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<T>? /** * Copies contents of this module to the given [collector]. @@ -69,8 +72,10 @@ public sealed class SerializersModule { /** * A [SerializersModule] which is empty and always returns `null`. */ -@SharedImmutable -@ExperimentalSerializationApi +@Deprecated("Deprecated in the favour of 'EmptySerializersModule()'", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("EmptySerializersModule()")) +@JsName("EmptySerializersModuleLegacyJs") // Compatibility with JS public val EmptySerializersModule: SerializersModule = SerialModuleImpl(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap()) /** @@ -122,7 +127,7 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri override fun <Base : Any> polymorphicDefaultDeserializer( baseClass: KClass<Base>, - defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>? + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>? ) { registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, allowOverwrite = true) } @@ -146,7 +151,7 @@ internal class SerialModuleImpl( ) : SerializersModule() { override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, value: T): SerializationStrategy<T>? { - if (!value.isInstanceOf(baseClass)) return null + if (!baseClass.isInstance(value)) return null // Registered val registered = polyBase2Serializers[baseClass]?.get(value::class) as? SerializationStrategy<T> if (registered != null) return registered @@ -154,7 +159,7 @@ internal class SerialModuleImpl( return (polyBase2DefaultSerializerProvider[baseClass] as? PolymorphicSerializerProvider<T>)?.invoke(value) } - override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<out T>? { + override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<T>? { // Registered val registered = polyBase2NamedSerializers[baseClass]?.get(serializedClassName) as? KSerializer<out T> if (registered != null) return registered @@ -197,7 +202,7 @@ internal class SerialModuleImpl( } } -internal typealias PolymorphicDeserializerProvider<Base> = (className: String?) -> DeserializationStrategy<out Base>? +internal typealias PolymorphicDeserializerProvider<Base> = (className: String?) -> DeserializationStrategy<Base>? internal typealias PolymorphicSerializerProvider<Base> = (value: Base) -> SerializationStrategy<Base>? /** This class is needed to support re-registering the same static (argless) serializers: diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt index 4c14f4b9..dfb9d819 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt @@ -34,6 +34,12 @@ 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 + +/** * A builder class for [SerializersModule] DSL. To create an instance of builder, use [SerializersModule] factory function. */ @OptIn(ExperimentalSerializationApi::class) @@ -92,11 +98,13 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser /** * Adds a default serializers provider associated with the given [baseClass] to the resulting module. - * [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` were found. + * [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` in the scope of [baseClass] were found. + * + * Default serializers provider affects only serialization process. To affect deserialization process, use + * [SerializersModuleBuilder.polymorphicDefaultDeserializer]. * - * This will not affect deserialization. + * [defaultSerializerProvider] can be stateful and lookup a serializer for the missing type dynamically. */ - @ExperimentalSerializationApi public override fun <Base : Any> polymorphicDefaultSerializer( baseClass: KClass<Base>, defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>? @@ -107,17 +115,19 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser /** * Adds a default deserializers provider associated with the given [baseClass] to the resulting module. * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className` - * were found. `className` could be `null` for formats that support nullable class discriminators + * in the scope of [baseClass] were found. `className` could be `null` for formats that support nullable class discriminators * (currently only `Json` with `useArrayPolymorphism` set to `false`). * - * This will not affect serialization. + * Default deserializers provider affects only deserialization process. To affect serialization process, use + * [SerializersModuleBuilder.polymorphicDefaultSerializer]. + * + * [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically. * * @see PolymorphicModuleBuilder.defaultDeserializer */ - @ExperimentalSerializationApi public override fun <Base : Any> polymorphicDefaultDeserializer( baseClass: KClass<Base>, - defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>? + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>? ) { registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, false) } @@ -163,7 +173,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser @JvmName("registerDefaultPolymorphicDeserializer") // Don't mangle method name for prettier stack traces internal fun <Base : Any> registerDefaultPolymorphicDeserializer( baseClass: KClass<Base>, - defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?, + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?, allowOverwrite: Boolean ) { val previous = polyBase2DefaultDeserializerProvider[baseClass] diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt index c4af77f8..c33d45a4 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt @@ -47,13 +47,13 @@ public interface SerializersModuleCollector { /** * Accept a default serializer provider, associated with the [baseClass] for polymorphic serialization. + * [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` in the scope of [baseClass] were found. * - * This will not affect deserialization. + * Default serializers provider affects only serialization process. Deserializers are accepted in the + * [SerializersModuleCollector.polymorphicDefaultDeserializer] method. * - * @see SerializersModuleBuilder.polymorphicDefaultSerializer - * @see PolymorphicModuleBuilder.defaultSerializer + * [defaultSerializerProvider] can be stateful and lookup a serializer for the missing type dynamically. */ - @ExperimentalSerializationApi public fun <Base : Any> polymorphicDefaultSerializer( baseClass: KClass<Base>, defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>? @@ -61,30 +61,43 @@ public interface SerializersModuleCollector { /** * Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization. + * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className` + * in the scope of [baseClass] were found. `className` could be `null` for formats that support nullable class discriminators + * (currently only `Json` with `useArrayPolymorphism` set to `false`). * - * This will not affect serialization. + * Default deserializers provider affects only deserialization process. Serializers are accepted in the + * [SerializersModuleCollector.polymorphicDefaultSerializer] method. * - * @see SerializersModuleBuilder.polymorphicDefaultDeserializer - * @see PolymorphicModuleBuilder.defaultDeserializer + * [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically. */ - @ExperimentalSerializationApi public fun <Base : Any> polymorphicDefaultDeserializer( baseClass: KClass<Base>, - defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>? + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>? ) /** * Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization. * - * This will not affect serialization. + * This function affect only deserialization process. To avoid confusion, it was deprecated and replaced with [polymorphicDefaultDeserializer]. + * To affect serialization process, use [SerializersModuleCollector.polymorphicDefaultSerializer]. * - * @see SerializersModuleBuilder.polymorphicDefaultDeserializer - * @see PolymorphicModuleBuilder.defaultDeserializer + * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className` + * in the scope of [baseClass] were found. `className` could be `null` for formats that support nullable class discriminators + * (currently only `Json` with `useArrayPolymorphism` set to `false`). + * + * [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically. + * + * @see SerializersModuleCollector.polymorphicDefaultDeserializer + * @see SerializersModuleCollector.polymorphicDefaultSerializer */ - // TODO: deprecate in 1.4 + @Deprecated( + "Deprecated in favor of function with more precise name: polymorphicDefaultDeserializer", + ReplaceWith("polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider)"), + DeprecationLevel.WARNING // Since 1.5.0. Raise to ERROR in 1.6.0, hide in 1.7.0 + ) public fun <Base : Any> polymorphicDefault( baseClass: KClass<Base>, - defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>? + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>? ) { polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider) } diff --git a/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt b/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt index fab9702f..caa2768f 100644 --- a/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt +++ b/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt @@ -4,12 +4,15 @@ package kotlinx.serialization +import kotlinx.serialization.builtins.NothingSerializer +import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME -import kotlinx.serialization.internal.* import kotlinx.serialization.modules.* +import kotlinx.serialization.test.* import kotlin.test.* +import kotlin.time.Duration /* * Test ensures that type that aggregate all basic (primitive/collection/maps/arrays) @@ -20,7 +23,7 @@ class BasicTypesSerializationTest { // KeyValue Input/Output class KeyValueOutput(private val sb: StringBuilder) : AbstractEncoder() { - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { sb.append('{') @@ -56,7 +59,7 @@ class BasicTypesSerializationTest { } class KeyValueInput(private val inp: Parser) : AbstractDecoder() { - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { inp.expectAfterWhiteSpace('{') @@ -170,4 +173,40 @@ class BasicTypesSerializationTest { assertEquals(umbrellaInstance, other) assertNotSame(umbrellaInstance, other) } + + @Test + fun testEncodeDuration() { + val sb = StringBuilder() + val out = KeyValueOutput(sb) + + val duration = Duration.parseIsoString("P4DT12H30M5S") + out.encodeSerializableValue(Duration.serializer(), duration) + + assertEquals("\"${duration.toIsoString()}\"", sb.toString()) + } + + @Test + fun testDecodeDuration() { + val durationString = "P4DT12H30M5S" + val inp = KeyValueInput(Parser(StringReader("\"$durationString\""))) + val other = inp.decodeSerializableValue(Duration.serializer()) + assertEquals(Duration.parseIsoString(durationString), other) + } + + @Test + fun testNothingSerialization() { + // impossible to deserialize Nothing + assertFailsWith(SerializationException::class, "'kotlin.Nothing' does not have instances") { + val inp = KeyValueInput(Parser(StringReader("42"))) + @Suppress("IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION") + inp.decodeSerializableValue(NothingSerializer()) + } + + // it is possible to serialize only `null` for `Nothing?` + val sb = StringBuilder() + val out = KeyValueOutput(sb) + out.encodeNullableSerializableValue(NothingSerializer(), null) + assertEquals("null", sb.toString()) + } + } diff --git a/core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt b/core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt index d04390af..212169e6 100644 --- a/core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt +++ b/core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt @@ -4,8 +4,10 @@ package kotlinx.serialization -import kotlinx.serialization.test.noJsLegacy +import kotlinx.serialization.modules.* +import kotlinx.serialization.test.* import kotlin.test.* +import kotlin.time.* class CachedSerializersTest { @Serializable @@ -20,18 +22,60 @@ class CachedSerializersTest { @Serializable abstract class Abstract + @Serializable + enum class SerializableEnum {A, B} + + @SerialInfo + annotation class MyAnnotation(val x: Int) + + @Serializable + enum class SerializableMarkedEnum { + @SerialName("first") + @MyAnnotation(1) + C, + @MyAnnotation(2) + D + } + @Test - fun testObjectSerializersAreSame() = noJsLegacy { + fun testObjectSerializersAreSame() { assertSame(Object.serializer(), Object.serializer()) } @Test - fun testSealedSerializersAreSame() = noJsLegacy { + fun testSerializableEnumSerializersAreSame() { + assertSame(SerializableEnum.serializer(), SerializableEnum.serializer()) + } + + @Test + fun testSerializableMarkedEnumSerializersAreSame() { + assertSame(SerializableMarkedEnum.serializer(), SerializableMarkedEnum.serializer()) + } + + @Test + fun testSealedSerializersAreSame() { assertSame(SealedParent.serializer(), SealedParent.serializer()) } @Test - fun testAbstractSerializersAreSame() = noJsLegacy { + fun testAbstractSerializersAreSame() { assertSame(Abstract.serializer(), Abstract.serializer()) } + + + @OptIn(ExperimentalTime::class) + @Test + fun testSerializersAreIntrinsified() = jvmOnly { + val m = SerializersModule { } + val direct = measureTime { + Object.serializer() + } + val directMs = direct.inWholeMicroseconds + val indirect = measureTime { + m.serializer<Object>() + } + val indirectMs = indirect.inWholeMicroseconds + if (indirectMs > directMs + (directMs / 4)) error("Direct ($directMs) and indirect ($indirectMs) times are too far apart") + } } + diff --git a/core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt b/core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt index 714c04c0..12721ddd 100644 --- a/core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt +++ b/core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt @@ -108,9 +108,8 @@ class CustomPropertyAccessorsTest { */ - private class CommonStringDecoder(private val elementCount: Int) : AbstractDecoder() { - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() private var elementIndex = 0 override fun decodeString(): String { diff --git a/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt b/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt index a22be3ff..3fee5873 100644 --- a/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt +++ b/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt @@ -2,10 +2,11 @@ package kotlinx.serialization import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.CompositeDecoder -import kotlinx.serialization.internal.ElementMarker +import kotlinx.serialization.internal.* import kotlin.test.Test import kotlin.test.assertEquals +@OptIn(CoreFriendModuleApi::class) class ElementMarkerTest { @Test fun testNothingWasRead() { diff --git a/core/commonTest/src/kotlinx/serialization/EnumDescriptorsTest.kt b/core/commonTest/src/kotlinx/serialization/EnumDescriptorsTest.kt new file mode 100644 index 00000000..5a3d452c --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/EnumDescriptorsTest.kt @@ -0,0 +1,101 @@ +/* + * 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.descriptors.SerialDescriptor +import kotlin.test.Test +import kotlin.test.assertEquals + + +class EnumDescriptorsTest { + + @Serializable + enum class SerializableEnum { + A, + B + } + + @SerialInfo + @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) + annotation class SerialAnnotation(val text: String) + + @SerialAnnotation("On Class") + @Serializable + enum class FullyAnnotatedEnum { + @SerialAnnotation("On A") + A, + + @SerialAnnotation("On B") + B + } + + @Serializable + enum class EntriesAnnotatedEnum { + @SerialAnnotation("On A") + A, + + @SerialAnnotation("On B") + B + } + + @SerialAnnotation("On Class") + @Serializable + enum class ClassAnnotatedEnum { + A, + B + } + + @Test + fun testSerializableEnum() { + val d = SerializableEnum.serializer().descriptor + assertEquals("kotlinx.serialization.EnumDescriptorsTest.SerializableEnum", d.serialName) + + assertEquals("A", d.getElementName(0)) + assertEquals("B", d.getElementName(1)) + } + + @Test + fun testFullyAnnotatedEnum() { + assertFullyAnnotated(FullyAnnotatedEnum.serializer().descriptor) + assertFullyAnnotated(serializer<FullyAnnotatedEnum>().descriptor) + } + + @Test + fun testEntriesAnnotatedEnum() { + assertEntriesAnnotated(EntriesAnnotatedEnum.serializer().descriptor) + assertEntriesAnnotated(serializer<EntriesAnnotatedEnum>().descriptor) + } + + @Test + fun testClassAnnotatedEnum() { + assertClassAnnotated(ClassAnnotatedEnum.serializer().descriptor) + assertClassAnnotated(serializer<ClassAnnotatedEnum>().descriptor) + } + + private fun assertFullyAnnotated(descriptor: SerialDescriptor) { + assertEquals(1, descriptor.annotations.size) + assertEquals("On Class", (descriptor.annotations.first() as SerialAnnotation).text) + + assertEquals(1, descriptor.getElementAnnotations(0).size) + assertEquals("On A", (descriptor.getElementAnnotations(0).first() as SerialAnnotation).text) + + assertEquals(1, descriptor.getElementAnnotations(1).size) + assertEquals("On B", (descriptor.getElementAnnotations(1).first() as SerialAnnotation).text) + } + + private fun assertEntriesAnnotated(descriptor: SerialDescriptor) { + assertEquals(1, descriptor.getElementAnnotations(0).size) + assertEquals("On A", (descriptor.getElementAnnotations(0).first() as SerialAnnotation).text) + + assertEquals(1, descriptor.getElementAnnotations(1).size) + assertEquals("On B", (descriptor.getElementAnnotations(1).first() as SerialAnnotation).text) + } + + private fun assertClassAnnotated(descriptor: SerialDescriptor) { + assertEquals(1, descriptor.annotations.size) + assertEquals("On Class", (descriptor.annotations.first() as SerialAnnotation).text) + } + +} diff --git a/core/commonTest/src/kotlinx/serialization/InheritableSerialInfoTest.kt b/core/commonTest/src/kotlinx/serialization/InheritableSerialInfoTest.kt index a117ad48..cd5b7a0d 100644 --- a/core/commonTest/src/kotlinx/serialization/InheritableSerialInfoTest.kt +++ b/core/commonTest/src/kotlinx/serialization/InheritableSerialInfoTest.kt @@ -1,7 +1,6 @@ package kotlinx.serialization import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.test.isJsLegacy import kotlin.test.* @@ -33,7 +32,6 @@ class InheritableSerialInfoTest { class E3: A, B private fun doTest(descriptor: SerialDescriptor) { - if (isJsLegacy()) return // Unsupported val list = descriptor.annotations.filterIsInstance<InheritableDiscriminator>() assertEquals(1, list.size) assertEquals("a", list.first().discriminator) diff --git a/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt new file mode 100644 index 00000000..071045a8 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt @@ -0,0 +1,56 @@ +package kotlinx.serialization + +import kotlinx.serialization.test.* +import kotlin.reflect.KClass +import kotlin.test.* + +@MetaSerializable +@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) +annotation class MySerializable + +@MetaSerializable +@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) +annotation class MySerializableWithInfo( + val value: Int, + val kclass: KClass<*> +) + + +class MetaSerializableTest { + + @MySerializable + class Project1(val name: String, val language: String) + + @MySerializableWithInfo(123, String::class) + class Project2(val name: String, val language: String) + + @MySerializableWithInfo(123, String::class) + @Serializable + class Project3(val name: String, val language: String) + + @Serializable + class Wrapper( + @MySerializableWithInfo(234, Int::class) val project: Project3 + ) + + @Test + fun testMetaSerializable() { + val serializer = serializer<Project1>() + assertNotNull(serializer) + } + + @Test + fun testMetaSerializableWithInfo() { + val info = serializer<Project2>().descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first() + assertEquals(123, info.value) + assertEquals(String::class, info.kclass) + } + + @Test + fun testMetaSerializableOnProperty() { + val info = serializer<Wrapper>().descriptor + .getElementAnnotations(0).filterIsInstance<MySerializableWithInfo>().first() + assertEquals(234, info.value) + assertEquals(Int::class, info.kclass) + } +} diff --git a/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt b/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt index 393e2b59..22de3a4d 100644 --- a/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt +++ b/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt @@ -30,7 +30,6 @@ open class PolyBase(val id: Int) { @Serializable data class PolyDerived(val s: String) : PolyBase(1) -@SharedImmutable val BaseAndDerivedModule = SerializersModule { polymorphic(PolyBase::class, PolyBase.serializer()) { subclass(PolyDerived.serializer()) diff --git a/core/commonTest/src/kotlinx/serialization/PrimitiveSerialDescriptorTest.kt b/core/commonTest/src/kotlinx/serialization/PrimitiveSerialDescriptorTest.kt new file mode 100644 index 00000000..deafc085 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/PrimitiveSerialDescriptorTest.kt @@ -0,0 +1,29 @@ +package kotlinx.serialization + +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.internal.PrimitiveSerialDescriptor +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotSame + +class PrimitiveSerialDescriptorTest { + + @Test + fun testEqualsImplemented() { + val first = PrimitiveSerialDescriptor("test_name", PrimitiveKind.LONG) + val second = PrimitiveSerialDescriptor("test_name", PrimitiveKind.LONG) + + assertNotSame(first, second) + assertEquals(first, second) + } + + @Test + fun testHashCodeStability() { + val first = PrimitiveSerialDescriptor("test_name", PrimitiveKind.LONG) + val second = PrimitiveSerialDescriptor("test_name", PrimitiveKind.LONG) + + assertNotSame(first, second) + assertEquals(first.hashCode(), second.hashCode()) + } + +} diff --git a/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt b/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt index f2010a2d..770ac50b 100644 --- a/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt +++ b/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt @@ -5,7 +5,6 @@ package kotlinx.serialization import kotlinx.serialization.descriptors.* -import kotlinx.serialization.test.isJsLegacy import kotlin.test.* class SerialDescriptorAnnotationsTest { @@ -103,7 +102,6 @@ class SerialDescriptorAnnotationsTest { class Holder(val r: Result, val a: AbstractResult, val o: ObjectResult, @Contextual val names: WithNames) private fun doTest(position: Int, expected: String) { - if (isJsLegacy()) return // Unsupported val desc = Holder.serializer().descriptor.getElementDescriptor(position) assertEquals(expected, desc.annotations.getCustom()) } diff --git a/core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt b/core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt index 78b015b5..1ff2a7ba 100644 --- a/core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt +++ b/core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt @@ -87,4 +87,11 @@ class SerialDescriptorBuilderTest { assertFailsWith<IllegalArgumentException> { PrimitiveSerialDescriptor(" ", PrimitiveKind.STRING) } assertFailsWith<IllegalArgumentException> { PrimitiveSerialDescriptor("\t", PrimitiveKind.STRING) } } + + @Test + fun testNullableBuild() { + val descriptor = buildClassSerialDescriptor("my.Simple") {}.nullable + assertTrue(descriptor.isNullable) + assertEquals("my.Simple?", descriptor.serialName) + } } diff --git a/core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt index 4fb61b04..5f5a6f7b 100644 --- a/core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt +++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt @@ -6,6 +6,7 @@ package kotlinx.serialization import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* +import kotlinx.serialization.internal.EnumSerializer import kotlinx.serialization.test.* import kotlin.test.* @@ -15,8 +16,7 @@ class SerializersLookupEnumTest { @Serializable(with = EnumExternalObjectSerializer::class) enum class EnumExternalObject - @Serializer(forClass = EnumExternalObject::class) - object EnumExternalObjectSerializer { + object EnumExternalObjectSerializer: KSerializer<EnumExternalObject> { override val descriptor: SerialDescriptor = buildSerialDescriptor("tmp", SerialKind.ENUM) override fun serialize(encoder: Encoder, value: EnumExternalObject) { @@ -28,11 +28,10 @@ class SerializersLookupEnumTest { } } - @Serializable(with = EnumExternalClassSerializer::class) + @Serializable(with = EnumCustomClassSerializer::class) enum class EnumExternalClass - @Serializer(forClass = EnumExternalClass::class) - class EnumExternalClassSerializer { + class EnumCustomClassSerializer: KSerializer<EnumExternalClass> { override val descriptor: SerialDescriptor = buildSerialDescriptor("tmp", SerialKind.ENUM) override fun serialize(encoder: Encoder, value: EnumExternalClass) { @@ -44,45 +43,47 @@ class SerializersLookupEnumTest { } } - @Polymorphic - enum class EnumPolymorphic - @Serializable enum class PlainEnum + @Serializable + enum class SerializableEnum { C, D } + + @Serializable + enum class SerializableMarkedEnum { C, @SerialName("NotD") D } + @Test fun testPlainEnum() { - assertEquals(PlainEnum.serializer(), serializer<PlainEnum>()) + assertSame(PlainEnum.serializer(), serializer<PlainEnum>()) + + if (!isJs()) { + assertIs<EnumSerializer<PlainEnum>>(serializer<PlainEnum>()) + } } @Test - fun testEnumExternalObject() { - assertSame(EnumExternalObjectSerializer, EnumExternalObject.serializer()) - assertSame(EnumExternalObjectSerializer, serializer<EnumExternalObject>()) + fun testSerializableEnumSerializer() { + assertIs<EnumSerializer<SerializableEnum>>(SerializableEnum.serializer()) + + assertSame(SerializableEnum.serializer(), serializer<SerializableEnum>()) } @Test - fun testEnumExternalClass() { - assertIs<EnumExternalClassSerializer>(EnumExternalClass.serializer()) + fun testSerializableMarkedEnumSerializer() { + assertIs<EnumSerializer<SerializableMarkedEnum>>(SerializableMarkedEnum.serializer()) - if (isJvm()) { - assertIs<EnumExternalClassSerializer>(serializer<EnumExternalClass>()) - } else if (isJsIr() || isNative()) { - // FIXME serializer<EnumWithClassSerializer> is broken for K/JS and K/Native. Remove `assertFails` after fix - assertFails { serializer<EnumExternalClass>() } - } + assertSame(SerializableMarkedEnum.serializer(), serializer<SerializableMarkedEnum>()) } @Test - fun testEnumPolymorphic() { - if (isJvm()) { - assertEquals( - PolymorphicSerializer(EnumPolymorphic::class).descriptor, - serializer<EnumPolymorphic>().descriptor - ) - } else { - // FIXME serializer<PolymorphicEnum> is broken for K/JS and K/Native. Remove `assertFails` after fix - assertFails { serializer<EnumPolymorphic>() } - } + fun testEnumExternalObject() { + assertSame(EnumExternalObjectSerializer, EnumExternalObject.serializer()) + assertSame(EnumExternalObjectSerializer, serializer<EnumExternalObject>()) + } + + @Test + fun testEnumExternalClass() { + assertIs<EnumCustomClassSerializer>(EnumExternalClass.serializer()) + assertIs<EnumCustomClassSerializer>(serializer<EnumExternalClass>()) } } diff --git a/core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt new file mode 100644 index 00000000..fc770575 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt @@ -0,0 +1,51 @@ +package kotlinx.serialization + +import kotlinx.serialization.test.* +import kotlin.test.* + +class SerializersLookupInterfaceTest { + + interface I + + @Polymorphic + interface I2 + + @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") + @Serializable(PolymorphicSerializer::class) + interface I3 + + @Serializable + @SerialName("S") + sealed interface S + + // TODO: not working because (see #1207, plugin does not produce companion object for interfaces) + // We even have #1853 with tests for that + // @Serializable(ExternalSerializer::class) + // interface External + + + @Test + fun testSealedInterfaceLookup() { + val serializer = serializer<S>() + assertTrue(serializer is SealedClassSerializer) + assertEquals("S", serializer.descriptor.serialName) + } + + @Test + fun testInterfaceLookup() { + // Native does not have KClass.isInterface + if (isNative() || isWasm()) return + + val serializer1 = serializer<I>() + assertTrue(serializer1 is PolymorphicSerializer) + assertEquals("kotlinx.serialization.Polymorphic<I>", serializer1.descriptor.serialName) + + val serializer2 = serializer<I2>() + assertTrue(serializer2 is PolymorphicSerializer) + assertEquals("kotlinx.serialization.Polymorphic<I2>", serializer2.descriptor.serialName) + + val serializer3 = serializer<I3>() + assertTrue(serializer3 is PolymorphicSerializer) + assertEquals("kotlinx.serialization.Polymorphic<I3>", serializer3.descriptor.serialName) + } +} diff --git a/core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt new file mode 100644 index 00000000..65324c4c --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED") + +package kotlinx.serialization + +import kotlinx.serialization.builtins.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.* +import kotlinx.serialization.internal.* +import kotlinx.serialization.test.* +import kotlin.reflect.* +import kotlin.test.* + +class SerializersLookupNamedCompanionTest { + @Serializable + class Plain(val i: Int) { + companion object Named + } + + @Serializable + class Parametrized<T>(val value: T) { + companion object Named + } + + + @Serializer(forClass = PlainWithCustom::class) + object PlainSerializer + + @Serializable(PlainSerializer::class) + class PlainWithCustom(val i: Int) { + companion object Named + } + + class ParametrizedSerializer<T : Any>(val serializer: KSerializer<T>) : KSerializer<ParametrizedWithCustom<T>> { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("parametrized (${serializer.descriptor})", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): ParametrizedWithCustom<T> = TODO("Not yet implemented") + override fun serialize(encoder: Encoder, value: ParametrizedWithCustom<T>) = TODO("Not yet implemented") + } + + @Serializable(ParametrizedSerializer::class) + class ParametrizedWithCustom<T>(val i: T) { + companion object Named + } + + @Serializable + sealed interface SealedInterface { + companion object Named + } + + @Serializable + sealed interface SealedInterfaceWithExplicitAnnotation { + @NamedCompanion + companion object Named + } + + + @Test + fun test() { + assertSame<KSerializer<*>>(Plain.serializer(), serializer(typeOf<Plain>())) + + shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) { + assertSame<KSerializer<*>>(PlainSerializer, serializer(typeOf<PlainWithCustom>())) + } + + shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) { + assertEquals( + Parametrized.serializer(Int.serializer()).descriptor.toString(), + serializer(typeOf<Parametrized<Int>>()).descriptor.toString() + ) + } + + shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) { + assertEquals( + ParametrizedWithCustom.serializer(Int.serializer()).descriptor.toString(), + serializer(typeOf<ParametrizedWithCustom<Int>>()).descriptor.toString() + ) + } + + shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) { + assertEquals( + SealedInterface.serializer().descriptor.toString(), + serializer(typeOf<SealedInterface>()).descriptor.toString() + ) + } + + // should fail because annotation @NamedCompanion will be placed again by the compilation plugin + // and they both will be placed into @Container annotation - thus they will be invisible to the runtime + shouldFail<SerializationException>(sinceKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) { + serializer(typeOf<SealedInterfaceWithExplicitAnnotation>()) + } + } + + +}
\ No newline at end of file diff --git a/core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt index 49efb912..38d2fbf8 100644 --- a/core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt +++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt @@ -11,11 +11,10 @@ import kotlin.test.* @Suppress("RemoveExplicitTypeArguments") // This is exactly what's being tested class SerializersLookupObjectTest { - @Serializable(with = ObjectExternalObjectSerializer::class) + @Serializable(with = ObjectCustomObjectSerializer::class) object ObjectExternalObject - @Serializer(forClass = ObjectExternalObject::class) - object ObjectExternalObjectSerializer { + object ObjectCustomObjectSerializer: KSerializer<ObjectExternalObject> { override val descriptor: SerialDescriptor = buildSerialDescriptor("tmp", StructureKind.OBJECT) override fun serialize(encoder: Encoder, value: ObjectExternalObject) { @@ -27,11 +26,10 @@ class SerializersLookupObjectTest { } } - @Serializable(with = ObjectExternalClassSerializer::class) + @Serializable(with = ObjectCustomClassSerializer::class) object ObjectExternalClass - @Serializer(forClass = ObjectExternalClass::class) - class ObjectExternalClassSerializer { + class ObjectCustomClassSerializer: KSerializer<ObjectExternalClass> { override val descriptor: SerialDescriptor = buildSerialDescriptor("tmp", StructureKind.OBJECT) override fun serialize(encoder: Encoder, value: ObjectExternalClass) { @@ -43,48 +41,24 @@ class SerializersLookupObjectTest { } } - @Polymorphic - object ObjectPolymorphic - @Serializable object PlainObject @Test fun testPlainObject() { - if (!isJsLegacy()) { - assertSame(PlainObject.serializer(), serializer<PlainObject>()) - } + assertSame(PlainObject.serializer(), serializer<PlainObject>()) } @Test fun testObjectExternalObject() { - assertSame(ObjectExternalObjectSerializer, ObjectExternalObject.serializer()) - if (!isJsLegacy()) { - assertSame(ObjectExternalObjectSerializer, serializer<ObjectExternalObject>()) - } + assertSame(ObjectCustomObjectSerializer, ObjectExternalObject.serializer()) + assertSame(ObjectCustomObjectSerializer, serializer<ObjectExternalObject>()) } @Test fun testObjectExternalClass() { - assertIs<ObjectExternalClassSerializer>(ObjectExternalClass.serializer()) - - if (!isJsLegacy()) { - assertIs<ObjectExternalClassSerializer>(serializer<ObjectExternalClass>()) - } - } - - @Test - fun testEnumPolymorphic() { - if (isJvm()) { - assertEquals( - PolymorphicSerializer(ObjectPolymorphic::class).descriptor, - serializer<ObjectPolymorphic>().descriptor - ) - } else { - // FIXME serializer<PolymorphicObject> is broken for K/JS and K/Native. Remove `assertFails` after fix - assertFails { serializer<ObjectPolymorphic>() } - } - + assertIs<ObjectCustomClassSerializer>(ObjectExternalClass.serializer()) + assertIs<ObjectCustomClassSerializer>(serializer<ObjectExternalClass>()) } } diff --git a/core/commonTest/src/kotlinx/serialization/SerializersModuleTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersModuleTest.kt new file mode 100644 index 00000000..9e255f27 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/SerializersModuleTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization + +import kotlinx.serialization.builtins.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.internal.* +import kotlinx.serialization.modules.* +import kotlinx.serialization.test.* +import kotlin.reflect.* +import kotlin.test.* + +class SerializersModuleTest { + @Serializable + object Object + + @Serializable + sealed class SealedParent { + @Serializable + data class Child(val i: Int) : SealedParent() + } + + @Serializable + abstract class Abstract + + @Serializable + enum class SerializableEnum { A, B } + + @Serializable(CustomSerializer::class) + class WithCustomSerializer(val i: Int) + + @Serializer(forClass = WithCustomSerializer::class) + object CustomSerializer + + @Serializable + class Parametrized<T : Any>(val a: T) + + @Serializable + class ParametrizedOfNullable<T>(val a: T) + + class ContextualType(val i: Int) + + @Serializer(forClass = ContextualType::class) + object ContextualSerializer + + @Serializable + class ContextualHolder(@Contextual val contextual: ContextualType) + + @Test + fun testCompiled() { + assertSame<KSerializer<*>>(Object.serializer(), serializer(Object::class, emptyList(), false)) + assertSame<KSerializer<*>>(SealedParent.serializer(), serializer(SealedParent::class, emptyList(), false)) + assertSame<KSerializer<*>>( + SealedParent.Child.serializer(), + serializer(SealedParent.Child::class, emptyList(), false) + ) + + assertSame<KSerializer<*>>(Abstract.serializer(), serializer(Abstract::class, emptyList(), false)) + assertSame<KSerializer<*>>(SerializableEnum.serializer(), serializer(SerializableEnum::class, emptyList(), false)) + } + + @Test + fun testBuiltIn() { + assertSame<KSerializer<*>>(Int.serializer(), serializer(Int::class, emptyList(), false)) + } + + @Test + fun testCustom() { + val m = SerializersModule { } + assertSame<KSerializer<*>>(CustomSerializer, m.serializer(WithCustomSerializer::class, emptyList(), false)) + } + + @Test + fun testParametrized() { + val serializer = serializer(Parametrized::class, listOf(Int.serializer()), false) + assertEquals<KClass<*>>(Parametrized.serializer(Int.serializer())::class, serializer::class) + assertEquals(PrimitiveKind.INT, serializer.descriptor.getElementDescriptor(0).kind) + + val mapSerializer = serializer(Map::class, listOf(String.serializer(), Int.serializer()), false) + assertIs<MapLikeSerializer<*, *, *, *>>(mapSerializer) + assertEquals(PrimitiveKind.STRING, mapSerializer.descriptor.getElementDescriptor(0).kind) + assertEquals(PrimitiveKind.INT, mapSerializer.descriptor.getElementDescriptor(1).kind) + } + + @Suppress("UNCHECKED_CAST") + @Test + fun testNothingAndParameterizedOfNothing() { + assertEquals(NothingSerializer, Nothing::class.serializer()) + //assertEquals(NothingSerializer, serializer<Nothing>()) // prohibited by compiler + assertEquals(NothingSerializer, serializer(Nothing::class, emptyList(), false) as KSerializer<Nothing>) + //assertEquals(NullableSerializer(NothingSerializer), serializer<Nothing?>()) // prohibited by compiler + assertEquals( + NullableSerializer(NothingSerializer), + serializer(Nothing::class, emptyList(), true) as KSerializer<Nothing?> + ) + + val parameterizedNothingSerializer = serializer<Parametrized<Nothing>>() + val nothingDescriptor = parameterizedNothingSerializer.descriptor.getElementDescriptor(0) + assertEquals(NothingSerialDescriptor, nothingDescriptor) + + val parameterizedNullableNothingSerializer = serializer<ParametrizedOfNullable<Nothing?>>() + val nullableNothingDescriptor = parameterizedNullableNothingSerializer.descriptor.getElementDescriptor(0) + assertEquals(SerialDescriptorForNullable(NothingSerialDescriptor), nullableNothingDescriptor) + } + + @Test + fun testUnsupportedArray() { + assertFails { + serializer(Array::class, listOf(Int.serializer()), false) + } + } + + @Suppress("UNCHECKED_CAST") + @Test + fun testContextual() { + val m = SerializersModule { + contextual<ContextualType>(ContextualSerializer) + contextual(ContextualGenericsTest.ThirdPartyBox::class) { args -> ContextualGenericsTest.ThirdPartyBoxSerializer(args[0]) } + } + + val contextualSerializer = m.serializer(ContextualType::class, emptyList(), false) + assertSame<KSerializer<*>>(ContextualSerializer, contextualSerializer) + + val boxSerializer = m.serializer(ContextualGenericsTest.ThirdPartyBox::class, listOf(Int.serializer()), false) + assertIs<ContextualGenericsTest.ThirdPartyBoxSerializer<Int>>(boxSerializer) + assertEquals(PrimitiveKind.INT, boxSerializer.descriptor.getElementDescriptor(0).kind) + + val holderSerializer = m.serializer(ContextualHolder::class, emptyList(), false) + assertSame<KSerializer<*>>(ContextualHolder.serializer(), holderSerializer) + } + +} + diff --git a/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt b/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt index 61e42f61..ce9de637 100644 --- a/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt +++ b/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt @@ -67,7 +67,6 @@ data class ArraysUmbrella( arrIntData.contentEquals(other.arrIntData) } -@SharedImmutable val umbrellaInstance = TypesUmbrella( Unit, true, 10, 20, 30, 40, 50.1f, 60.1, 'A', "Str0", Attitude.POSITIVE, IntData(70), null, null, 11, 21, 31, 41, 51.1f, 61.1, 'B', "Str1", Attitude.NEUTRAL, null, @@ -87,4 +86,4 @@ val umbrellaInstance = TypesUmbrella( arrayOf(null, -1, -2), arrayOf(IntData(1), IntData(2)) ) -) +)
\ No newline at end of file diff --git a/core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt b/core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt index dece1704..c3d6ac00 100644 --- a/core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt +++ b/core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt @@ -7,6 +7,8 @@ package kotlinx.serialization.features import kotlinx.serialization.* import kotlinx.serialization.builtins.* import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.internal.* import kotlinx.serialization.test.EnumSerializer import kotlin.test.* @@ -18,15 +20,17 @@ class SchemaTest { @Serializable data class Box<T>(val boxed: T) - @Serializable + @Serializable(Data1.Companion::class) data class Data1(val l: List<Int> = emptyList(), val s: String) { - @Serializer(forClass = Data1::class) - companion object { - // TODO removal of explicit type crashes the compiler - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Data1") { + companion object: KSerializer<Data1> { + override val descriptor = buildClassSerialDescriptor("Data1") { element("l", listSerialDescriptor<Int>(), isOptional = true) element("s", serialDescriptor<String>()) } + + override fun serialize(encoder: Encoder, value: Data1) = error("Should not be called") + + override fun deserialize(decoder: Decoder): Data1 = error("Should not be called") } } diff --git a/core/commonTest/src/kotlinx/serialization/features/SealedInterfacesSerializationTest.kt b/core/commonTest/src/kotlinx/serialization/features/SealedInterfacesSerializationTest.kt new file mode 100644 index 00000000..433f9baf --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/features/SealedInterfacesSerializationTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("SERIALIZER_TYPE_INCOMPATIBLE") + +package kotlinx.serialization.features + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.test.* +import kotlin.test.Test +import kotlin.test.assertEquals + + +class SealedInterfacesSerializationTest { + interface A + + sealed interface B + + @Serializable + sealed interface C + + @Serializable(DummySerializer::class) + sealed interface D + + @Serializable(DummySerializer::class) + interface E + + @Serializable + @Polymorphic + sealed interface F + + @Serializable + class ImplA : A, B, C, D, E, F + + @Serializable + class ImplB : A, B, C, D, E, F + + @Serializable + class Holder( + val a: A, + val b: B, + val c: C, + val d: D, + val e: E, + @Polymorphic val polyC: C, + val f: F + ) + + class DummySerializer : KSerializer<Any> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Dummy") + + override fun serialize(encoder: Encoder, value: Any) { + error("serialize") + } + + override fun deserialize(decoder: Decoder): Any { + error("deserialize") + } + } + + private fun SerialDescriptor.haveSealedSubclasses() { + assertEquals(PolymorphicKind.SEALED, kind) + val subclasses = getElementDescriptor(1).elementDescriptors.map { it.serialName.substringAfterLast('.') } + assertEquals(listOf("ImplA", "ImplB"), subclasses) + } + + private fun SerialDescriptor.isDummy() = serialName == "Dummy" + + private fun SerialDescriptor.isPolymorphic() = kind == PolymorphicKind.OPEN + + operator fun SerialDescriptor.get(i: Int) = getElementDescriptor(i) + + @Test + fun testInHolder() { + val desc = Holder.serializer().descriptor + desc[0].isPolymorphic() + desc[1].isPolymorphic() + desc[2].haveSealedSubclasses() + desc[3].isDummy() + desc[4].isDummy() + desc[5].isPolymorphic() + desc[6].isPolymorphic() + } + + @Test + fun testGenerated() { + C.serializer().descriptor.haveSealedSubclasses() + } + + @Test + fun testResolved() { + serializer<C>().descriptor.haveSealedSubclasses() + } + + +} diff --git a/core/commonTest/src/kotlinx/serialization/internal/DummySequentialDecoder.kt b/core/commonTest/src/kotlinx/serialization/internal/DummySequentialDecoder.kt new file mode 100644 index 00000000..696883c4 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/internal/DummySequentialDecoder.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.internal + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.* + +/** + * The purpose of this decoder is to check whether its methods were called currectly, + * rather than implement any concrete format. + */ +class DummySequentialDecoder( + override val serializersModule: SerializersModule = EmptySerializersModule() +) : Decoder, CompositeDecoder { + private fun notImplemented(): Nothing = throw Error("Implement this method if needed") + + override fun decodeSequentially(): Boolean = true + override fun decodeElementIndex(descriptor: SerialDescriptor): Int = throw Error("This method shouldn't be called in sequential mode") + + var beginStructureCalled = 0 + var endStructureCalled = 0 + + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { + ++beginStructureCalled + return this + } + override fun endStructure(descriptor: SerialDescriptor): Unit { + ++endStructureCalled + return Unit + } + + override fun decodeInline(descriptor: SerialDescriptor): Decoder = notImplemented() + + override fun decodeBoolean(): Boolean = notImplemented() + override fun decodeByte(): Byte = notImplemented() + override fun decodeShort(): Short = notImplemented() + override fun decodeInt(): Int = notImplemented() + override fun decodeLong(): Long = notImplemented() + override fun decodeFloat(): Float = notImplemented() + override fun decodeDouble(): Double = notImplemented() + override fun decodeChar(): Char = notImplemented() + override fun decodeString(): String = notImplemented() + override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = notImplemented() + + override fun decodeNotNullMark(): Boolean = notImplemented() + override fun decodeNull(): Nothing? = notImplemented() + + override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean = notImplemented() + override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte = notImplemented() + override fun decodeShortElement(descriptor: SerialDescriptor, index: Int): Short = notImplemented() + override fun decodeIntElement(descriptor: SerialDescriptor, index: Int): Int = notImplemented() + override fun decodeLongElement(descriptor: SerialDescriptor, index: Int): Long = notImplemented() + override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int): Float = notImplemented() + override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int): Double = notImplemented() + override fun decodeCharElement(descriptor: SerialDescriptor, index: Int): Char = notImplemented() + override fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String = notImplemented() + + override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder = notImplemented() + override fun <T : Any?> decodeSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>, previousValue: T?): T = decodeSerializableValue(deserializer) + override fun <T : Any> decodeNullableSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T?>, previousValue: T?): T? = notImplemented() +} diff --git a/core/commonTest/src/kotlinx/serialization/internal/ObjectSerializerTest.kt b/core/commonTest/src/kotlinx/serialization/internal/ObjectSerializerTest.kt new file mode 100644 index 00000000..2234b8f5 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/internal/ObjectSerializerTest.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.internal + +import kotlin.test.* +import kotlinx.serialization.* + +class ObjectSerializerTest { + @Test + fun testSequentialDecoding() { + SimpleObject.serializer().deserialize(DummySequentialDecoder()) + } + + @Serializable + object SimpleObject +} diff --git a/core/commonTest/src/kotlinx/serialization/internal/TuplesTest.kt b/core/commonTest/src/kotlinx/serialization/internal/TuplesTest.kt new file mode 100644 index 00000000..ea0804c5 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/internal/TuplesTest.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.internal + +import kotlin.test.* +import kotlinx.serialization.builtins.* + +class TuplesTest { + @Test + fun testSequentialDecodingKeyValue() { + val decoder = DummySequentialDecoder() + val serializer = MapEntrySerializer(Unit.serializer(), Unit.serializer()) + serializer.deserialize(decoder) + assertEquals(decoder.beginStructureCalled, decoder.endStructureCalled) + } +} diff --git a/core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt b/core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt new file mode 100644 index 00000000..7bd35c17 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt @@ -0,0 +1,192 @@ +/* + * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.test + +import kotlin.test.* + +private val currentKotlinVersion = KotlinVersion.CURRENT + +private fun String.toKotlinVersion(): KotlinVersion { + val parts = split(".") + val intParts = parts.mapNotNull { it.toIntOrNull() } + if (parts.size != 3 || intParts.size != 3) error("Illegal kotlin version, expected format is 1.2.3") + + return KotlinVersion(intParts[0], intParts[1], intParts[2]) +} + +internal fun runSince(kotlinVersion: String, test: () -> Unit) { + if (currentKotlinVersion >= kotlinVersion.toKotlinVersion()) { + test() + } +} + + +internal inline fun <reified T : Throwable> shouldFail( + sinceKotlin: String? = null, + beforeKotlin: String? = null, + onJvm: Boolean = true, + onJs: Boolean = true, + onNative: Boolean = true, + onWasm: Boolean = true, + test: () -> Unit +) { + val args = mapOf( + "since" to sinceKotlin, + "before" to beforeKotlin, + "onJvm" to onJvm, + "onJs" to onJs, + "onNative" to onNative, + "onWasm" to onWasm + ) + + val sinceVersion = sinceKotlin?.toKotlinVersion() + val beforeVersion = beforeKotlin?.toKotlinVersion() + + val version = (sinceVersion != null && currentKotlinVersion >= sinceVersion) + || (beforeVersion != null && currentKotlinVersion < beforeVersion) + + val platform = (isJvm() && onJvm) || (isJs() && onJs) || (isNative() && onNative) || (isWasm() && onWasm) + + var error: Throwable? = null + try { + test() + } catch (e: Throwable) { + error = e + } + + if (version && platform) { + if (error == null) { + throw AssertionError("Exception with type '${T::class.simpleName}' expected for $args") + } + if (error !is T) throw AssertionError( + "Illegal exception type, expected '${T::class.simpleName}' actual '${error::class.simpleName}' for $args", + error + ) + } else { + if (error != null) throw AssertionError( + "Unexpected error for $args", + error + ) + } +} + +internal class CompilerVersionTest { + @Test + fun testSince() { + var executed = false + + runSince("1.0.0") { + executed = true + } + assertTrue(executed) + + executed = false + runSince("255.255.255") { + executed = true + } + assertFalse(executed) + } + + @Test + fun testFailBefore() { + // ok if there is no exception if current version greater is before of the specified + shouldFail<IllegalArgumentException>(beforeKotlin = "0.0.0") { + // no-op + } + + // error if there is no exception and if current version is before of the specified + assertFails { + shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255") { + // no-op + } + } + + // ok if thrown expected exception if current version is before of the specified + shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255") { + throw IllegalArgumentException() + } + + // ok if thrown unexpected exception if current version is before of the specified + assertFails { + shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255") { + throw Exception() + } + } + + } + + @Test + fun testFailSince() { + // ok if there is no exception if current version less then specified + shouldFail<IllegalArgumentException>(sinceKotlin = "255.255.255") { + // no-op + } + + // error if there is no exception and if current version is greater or equals specified + assertFails { + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0") { + // no-op + } + } + + // ok if thrown expected exception if current version is greater or equals specified + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0") { + throw IllegalArgumentException() + } + + // ok if thrown unexpected exception if current version is greater or equals specified + assertFails { + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0") { + throw Exception() + } + } + } + + @Test + fun testExcludePlatform() { + if (isJvm()) { + shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255", onJvm = false) { + // no-op + } + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", onJvm = false) { + // no-op + } + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", beforeKotlin = "255.255.255", onJvm = false) { + // no-op + } + } else if (isJs()) { + shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255", onJs = false) { + // no-op + } + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", onJs = false) { + // no-op + } + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", beforeKotlin = "255.255.255", onJs = false) { + // no-op + } + } else if (isWasm()) { + shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255", onWasm = false) { + // no-op + } + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", onWasm = false) { + // no-op + } + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", beforeKotlin = "255.255.255", onWasm = false) { + // no-op + } + } else if (isNative()) { + shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255", onNative = false) { + // no-op + } + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", onNative = false) { + // no-op + } + shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", beforeKotlin = "255.255.255", onNative = false) { + // no-op + } + } + } + +} diff --git a/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt b/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt index c47252d7..594ec0b4 100644 --- a/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt +++ b/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt @@ -5,13 +5,12 @@ package kotlinx.serialization.test enum class Platform { - JVM, JS_LEGACY, JS_IR, NATIVE + JVM, JS, NATIVE, WASM } public expect val currentPlatform: Platform -public fun isJs(): Boolean = currentPlatform == Platform.JS_LEGACY || currentPlatform == Platform.JS_IR -public fun isJsLegacy(): Boolean = currentPlatform == Platform.JS_LEGACY -public fun isJsIr(): Boolean = currentPlatform == Platform.JS_IR +public fun isJs(): Boolean = currentPlatform == Platform.JS public fun isJvm(): Boolean = currentPlatform == Platform.JVM public fun isNative(): Boolean = currentPlatform == Platform.NATIVE +public fun isWasm(): Boolean = currentPlatform == Platform.WASM diff --git a/core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt b/core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt index 660d1ef1..86974506 100644 --- a/core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt +++ b/core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt @@ -34,10 +34,6 @@ inline fun noJs(test: () -> Unit) { if (!isJs()) test() } -inline fun noJsLegacy(test: () -> Unit) { - if (!isJsLegacy()) test() -} - inline fun jvmOnly(test: () -> Unit) { if (isJvm()) test() } diff --git a/core/jsMain/src/kotlinx/serialization/Serializers.kt b/core/jsMain/src/kotlinx/serialization/SerializersJs.kt index 6f7547db..6f7547db 100644 --- a/core/jsMain/src/kotlinx/serialization/Serializers.kt +++ b/core/jsMain/src/kotlinx/serialization/SerializersJs.kt diff --git a/core/jsMain/src/kotlinx/serialization/internal/Platform.kt b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt index 25c48146..6bd63391 100644 --- a/core/jsMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt @@ -16,19 +16,35 @@ internal actual fun BooleanArray.getChecked(index: Int): Boolean { if (index !in indices) throw IndexOutOfBoundsException("Index $index out of bounds $indices") return get(index) } -@Suppress("UNCHECKED_CAST") + internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? = - this.constructSerializerForGivenTypeArgs() ?: this.js.asDynamic().Companion?.serializer() as? KSerializer<T> + this.constructSerializerForGivenTypeArgs() ?: ( + if (this === Nothing::class) NothingSerializer // Workaround for KT-51333 + else this.js.asDynamic().Companion?.serializer() + ) as? KSerializer<T> -internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> = toTypedArray() +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 Any.isInstanceOf(kclass: KClass<*>): Boolean = kclass.isInstance(this) +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 KClass<*>.platformSpecificSerializerNotRegistered(): Nothing { throw SerializationException( - "Serializer for class '${simpleName}' is not found.\n" + - "Mark the class as @Serializable or provide the serializer explicitly.\n" + - "On Kotlin/JS explicitly declared serializer should be used for interfaces and enums without @Serializable annotation" + notRegisteredMessage() + + "To get enum serializer on Kotlin/JS, it should be annotated with @Serializable annotation." ) } diff --git a/core/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/core/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt index b87276e8..23627d17 100644 --- a/core/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt +++ b/core/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt @@ -4,9 +4,4 @@ package kotlinx.serialization.test -public actual val currentPlatform: Platform = if (isLegacyBackend()) Platform.JS_LEGACY else Platform.JS_IR - -// from https://github.com/JetBrains/kotlin/blob/569187a7516e2e5ab217158a3170d4beb0c5cb5a/js/js.translator/testData/_commonFiles/testUtils.kt#L3 -private fun isLegacyBackend(): Boolean = - // Using eval to prevent DCE from thinking that following code depends on Kotlin module. - eval("(typeof Kotlin != \"undefined\" && typeof Kotlin.kotlin != \"undefined\")").unsafeCast<Boolean>() +public actual val currentPlatform: Platform = Platform.JS diff --git a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt index b110f121..b2d8da7c 100644 --- a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt +++ b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt @@ -17,65 +17,90 @@ 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) +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) +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. + * Retrieves a serializer for the given [type] using + * reflective construction and [contextual][SerializersModule.getContextual] lookup as a fallback 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]. - * - * 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() + 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. + * Retrieves a serializer for the given [type] using + * reflective construction and [contextual][SerializersModule.getContextual] lookup as a fallback 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]. - * - * 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): KSerializer<Any>? = +private fun SerializersModule.serializerByJavaTypeImpl( + type: Type, + failOnMissingTypeArgSerializer: Boolean = true +): KSerializer<Any>? = when (type) { is GenericArrayType -> { genericArraySerializer(type, failOnMissingTypeArgSerializer) @@ -85,7 +110,9 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing val rootClass = (type.rawType as Class<*>) val args = (type.actualTypeArguments) val argsSerializers = - if (failOnMissingTypeArgSerializer) args.map { serializer(it) } else args.map { serializerOrNull(it) ?: return null } + if (failOnMissingTypeArgSerializer) args.map { serializer(it) } else args.map { + serializerOrNull(it) ?: return null + } when { Set::class.java.isAssignableFrom(rootClass) -> SetSerializer(argsSerializers[0]) as KSerializer<Any> List::class.java.isAssignableFrom(rootClass) || Collection::class.java.isAssignableFrom(rootClass) -> ListSerializer( @@ -110,19 +137,20 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing ) 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) -private fun SerializersModule.typeSerializer(type: Class<*>, failOnMissingTypeArgSerializer: Boolean): KSerializer<Any>? { +private fun SerializersModule.typeSerializer( + type: Class<*>, + failOnMissingTypeArgSerializer: Boolean +): KSerializer<Any>? { return if (type.isArray && !type.componentType.isPrimitive) { val eType: Class<*> = type.componentType val s = if (failOnMissingTypeArgSerializer) serializer(eType) else (serializerOrNull(eType) ?: return null) @@ -134,7 +162,10 @@ private fun SerializersModule.typeSerializer(type: Class<*>, failOnMissingTypeAr } @OptIn(ExperimentalSerializationApi::class) -private fun <T : Any> SerializersModule.reflectiveOrContextual(jClass: Class<T>, typeArgumentsSerializers: List<KSerializer<Any?>>): KSerializer<T>? { +private fun <T : Any> SerializersModule.reflectiveOrContextual( + jClass: Class<T>, + typeArgumentsSerializers: List<KSerializer<Any?>> +): KSerializer<T>? { jClass.constructSerializerForGivenTypeArgs(*typeArgumentsSerializers.toTypedArray())?.let { return it } val kClass = jClass.kotlin return kClass.builtinSerializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers) @@ -165,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/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt b/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt new file mode 100644 index 00000000..191b30c1 --- /dev/null +++ b/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt @@ -0,0 +1,205 @@ +/* + * 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.lang.ref.SoftReference +import java.util.concurrent.ConcurrentHashMap +import kotlin.reflect.KClass +import kotlin.reflect.KClassifier +import kotlin.reflect.KType +import kotlin.reflect.KTypeProjection + +/* + * 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 = try { + Class.forName("java.lang.ClassValue") + true +} catch (_: Throwable) { + 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) +} + +private class ClassValueCache<T>(val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> { + private val classValue = ClassValueReferences<CacheEntry<T>>() + + override fun get(key: KClass<Any>): KSerializer<T>? { + return classValue + .getOrSet(key.java) { CacheEntry(compute(key)) } + .serializer + } +} + +/** + * A class that combines the capabilities of ClassValue and SoftReference. + * Softly binds the calculated value to the specified class. + * + * [SoftReference] used to prevent class loaders from leaking, + * since the value can transitively refer to an instance of type [Class], this may prevent the loader from + * being collected during garbage collection. + * + * In the first calculation the value is cached, every time [getOrSet] is called, a pre-calculated value is returned. + * + * However, the value can be collected during garbage collection (thanks to [SoftReference]) + * - in this case, when trying to call the [getOrSet] function, the value will be calculated again and placed in the cache. + * + * An important requirement for a function generating a value is that it must be stable, so that each time it is called for the same class, the function returns similar values. + * In the case of serializers, these should be instances of the same class filled with equivalent values. + */ +@SuppressAnimalSniffer +private class ClassValueReferences<T> : ClassValue<MutableSoftReference<T>>() { + override fun computeValue(type: Class<*>): MutableSoftReference<T> { + return MutableSoftReference() + } + + inline fun getOrSet(key: Class<*>, crossinline factory: () -> T): T { + val ref: MutableSoftReference<T> = get(key) + + ref.reference.get()?.let { return it } + + // go to the slow path and create serializer with blocking, also wrap factory block + return ref.getOrSetWithLock { factory() } + } + +} + +/** + * Wrapper over `SoftReference`, used to store a mutable value. + */ +private class MutableSoftReference<T> { + // volatile because of situations like https://stackoverflow.com/a/7855774 + @JvmField + @Volatile + var reference: SoftReference<T> = SoftReference(null) + + /* + It is important that the monitor for synchronized is the `MutableSoftReference` of a specific class + This way access to reference is blocked only for one serializable class, and not for all + */ + @Synchronized + fun getOrSetWithLock(factory: () -> T): T { + // exit function if another thread has already filled in the `reference` with non-null value + reference.get()?.let { return it } + + val value = factory() + reference = SoftReference(value) + return value + } +} + +private class ClassValueParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) : + ParametrizedSerializerCache<T> { + private val classValue = ClassValueReferences<ParametrizedCacheEntry<T>>() + + override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> { + return classValue.getOrSet(key.java) { ParametrizedCacheEntry() } + .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) } + } +} + +/** + * Wrapper for cacheable serializer of some type. + * Used to store cached serializer or indicates that the serializer is not cacheable. + * + * If serializer for type is not cacheable then value of [serializer] is `null`. + */ +private class CacheEntry<T>(@JvmField val serializer: KSerializer<T>?) + +/** + * Workaround of https://youtrack.jetbrains.com/issue/KT-54611 and https://github.com/Kotlin/kotlinx.serialization/issues/2065 + */ +private class KTypeWrapper(private val origin: KType) : KType { + override val annotations: List<Annotation> + get() = origin.annotations + override val arguments: List<KTypeProjection> + get() = origin.arguments + override val classifier: KClassifier? + get() = origin.classifier + override val isMarkedNullable: Boolean + get() = origin.isMarkedNullable + + override fun equals(other: Any?): Boolean { + if (other == null) return false + if (origin != (other as? KTypeWrapper)?.origin) return false + + val kClassifier = classifier + if (kClassifier is KClass<*>) { + val otherClassifier = (other as? KType)?.classifier + if (otherClassifier == null || otherClassifier !is KClass<*>) { + return false + } + return kClassifier.java == otherClassifier.java + } else { + return false + } + } + + override fun hashCode(): Int { + return origin.hashCode() + } + + override fun toString(): String { + return "KTypeWrapper: $origin" + } +} + +private class ParametrizedCacheEntry<T> { + private val serializers: ConcurrentHashMap<List<KTypeWrapper>, Result<KSerializer<T>?>> = ConcurrentHashMap() + inline fun computeIfAbsent(types: List<KType>, producer: () -> KSerializer<T>?): Result<KSerializer<T>?> { + val wrappedTypes = types.map { KTypeWrapper(it) } + return serializers.getOrPut(wrappedTypes) { + kotlin.runCatching { producer() } + } + } +} + diff --git a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt index 9dbb5a06..72ec9ea9 100644 --- a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.serialization.internal @@ -18,7 +18,6 @@ internal actual inline fun BooleanArray.getChecked(index: Int): Boolean { return get(index) } -@Suppress("UNCHECKED_CAST") internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? = this.constructSerializerForGivenTypeArgs() @@ -29,40 +28,58 @@ internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KCl internal actual fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing = serializerNotRegistered() internal fun Class<*>.serializerNotRegistered(): Nothing { - throw SerializationException( - "Serializer for class '${simpleName}' is not found.\n" + - "Mark the class as @Serializable or provide the serializer explicitly." - ) + throw SerializationException(this.kotlin.notRegisteredMessage()) } internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? { return java.constructSerializerForGivenTypeArgs(*args) } -@Suppress("UNCHECKED_CAST") internal fun <T: Any> Class<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? { if (isEnum && isNotAnnotated()) { return createEnumSerializer() } - if (isInterface) { - return interfaceSerializer() - } + // Fall-through if the serializer is not found -- lookup on companions (for sealed interfaces) or fallback to polymorphic if applicable + if (isInterface) interfaceSerializer()?.let { return it } // Search for serializer defined on companion object. - val serializer = invokeSerializerOnCompanion<T>(this, *args) + val serializer = invokeSerializerOnDefaultCompanion<T>(this, *args) if (serializer != null) return serializer // Check whether it's serializable object findObjectSerializer()?.let { return it } // Search for default serializer if no serializer is defined in companion object. // It is required for named companions - val fromNamedCompanion = try { + val fromNamedCompanion = findInNamedCompanion(*args) + if (fromNamedCompanion != null) return fromNamedCompanion + // Check for polymorphic + return if (isPolymorphicSerializer()) { + PolymorphicSerializer(this.kotlin) + } else { + null + } +} + +@Suppress("UNCHECKED_CAST") +private fun <T: Any> Class<T>.findInNamedCompanion(vararg args: KSerializer<Any?>): KSerializer<T>? { + val namedCompanion = findNamedCompanionByAnnotation() + if (namedCompanion != null) { + invokeSerializerOnCompanion<T>(namedCompanion, *args)?.let { return it } + } + + // fallback strategy for old compiler - try to locate plugin-generated singleton (without type parameters) serializer + return try { declaredClasses.singleOrNull { it.simpleName == ("\$serializer") } ?.getField("INSTANCE")?.get(null) as? KSerializer<T> } catch (e: NoSuchFieldException) { null } - if (fromNamedCompanion != null) return fromNamedCompanion - // Check for polymorphic - return polymorphicSerializer() +} + +private fun <T: Any> Class<T>.findNamedCompanionByAnnotation(): Any? { + val companionClass = declaredClasses.firstOrNull { clazz -> + clazz.getAnnotation(NamedCompanion::class.java) != null + } ?: return null + + return companionOrNull(companionClass.simpleName) } private fun <T: Any> Class<T>.isNotAnnotated(): Boolean { @@ -73,19 +90,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>? { @@ -101,9 +118,13 @@ private fun <T: Any> Class<T>.interfaceSerializer(): KSerializer<T>? { return null } +private fun <T : Any> invokeSerializerOnDefaultCompanion(jClass: Class<*>, vararg args: KSerializer<Any?>): KSerializer<T>? { + val companion = jClass.companionOrNull("Companion") ?: return null + return invokeSerializerOnCompanion(companion, *args) +} + @Suppress("UNCHECKED_CAST") -private fun <T : Any> invokeSerializerOnCompanion(jClass: Class<*>, vararg args: KSerializer<Any?>): KSerializer<T>? { - val companion = jClass.companionOrNull() ?: return null +private fun <T : Any> invokeSerializerOnCompanion(companion: Any, vararg args: KSerializer<Any?>): KSerializer<T>? { return try { val types = if (args.isEmpty()) emptyArray() else Array(args.size) { KSerializer::class.java } companion.javaClass.getDeclaredMethod("serializer", *types) @@ -116,9 +137,9 @@ private fun <T : Any> invokeSerializerOnCompanion(jClass: Class<*>, vararg args: } } -private fun Class<*>.companionOrNull() = +private fun Class<*>.companionOrNull(companionName: String) = try { - val companion = getDeclaredField("Companion") + val companion = getDeclaredField(companionName) companion.isAccessible = true companion.get(null) } catch (e: Throwable) { @@ -126,12 +147,15 @@ 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>? { + // Special case to avoid IllegalAccessException on Java11+ (#2449) + // There are no serializable objects in the stdlib anyway. + if (this.canonicalName?.let { it.startsWith("java.") || it.startsWith("kotlin.") } != false) return null // Check it is an object without using kotlin-reflect val field = declaredFields.singleOrNull { it.name == "INSTANCE" && it.type == this && Modifier.isStatic(it.modifiers) } @@ -146,18 +170,4 @@ private fun <T : Any> Class<T>.findObjectSerializer(): KSerializer<T>? { return result as? KSerializer<T> } -/** - * Checks if an [this@isInstanceOf] is an instance of a given [kclass]. - * - * This check is a replacement for [KClass.isInstance] because - * on JVM it requires kotlin-reflect.jar in classpath - * (see https://youtrack.jetbrains.com/issue/KT-14720). - * - * On JS and Native, this function delegates to aforementioned - * [KClass.isInstance] since it is supported there out-of-the box; - * on JVM, it falls back to java.lang.Class.isInstance, which causes - * difference when applied to function types with big arity. - */ -internal actual fun Any.isInstanceOf(kclass: KClass<*>): Boolean = kclass.javaObjectType.isInstance(this) - internal actual fun isReferenceArray(rootClass: KClass<Any>): Boolean = rootClass.java.isArray 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/jvmTest/src/kotlinx/serialization/CachingTest.kt b/core/jvmTest/src/kotlinx/serialization/CachingTest.kt new file mode 100644 index 00000000..b146c920 --- /dev/null +++ b/core/jvmTest/src/kotlinx/serialization/CachingTest.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization + +import kotlinx.serialization.internal.* +import kotlinx.serialization.modules.* +import org.junit.Test +import kotlin.reflect.* +import kotlin.test.* + +class CachingTest { + @Test + fun testCache() { + var factoryCalled = 0 + + val cache = createCache { + factoryCalled += 1 + it.serializerOrNull() + } + + repeat(10) { + cache.get(typeOf<String>().kclass()) + } + + assertEquals(1, factoryCalled) + } + + @Test + fun testParameterizedCache() { + var factoryCalled = 0 + + val cache = createParametrizedCache { clazz, types -> + factoryCalled += 1 + val serializers = EmptySerializersModule().serializersForParameters(types, true)!! + clazz.parametrizedSerializerOrNull(serializers) { types[0].classifier } + } + + repeat(10) { + cache.get(typeOf<Map<*, *>>().kclass(), listOf(typeOf<String>(), typeOf<String>())) + } + + assertEquals(1, factoryCalled) + } +} diff --git a/core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt b/core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt index 31eda2fa..332886d4 100644 --- a/core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt +++ b/core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt @@ -43,15 +43,17 @@ class SerializationMethodInvocationOrderTest { class Out : AbstractEncoder() { var step = 0 - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { when (step) { 1 -> { - checkContainerDesc(descriptor); step++; return this + checkContainerDesc(descriptor); step++ + return this } 4 -> { - checkDataDesc(descriptor); step++; return this + checkDataDesc(descriptor); step++ + return this } } fail("@$step: beginStructure($descriptor)") @@ -61,17 +63,20 @@ class SerializationMethodInvocationOrderTest { when (step) { 2 -> { checkContainerDesc(descriptor); if (index == 0) { - step++; return true + step++ + return true } } 5 -> { checkDataDesc(descriptor); if (index == 0) { - step++; return true + step++ + return true } } 7 -> { checkDataDesc(descriptor); if (index == 1) { - step++; return true + step++ + return true } } } @@ -80,29 +85,44 @@ class SerializationMethodInvocationOrderTest { override fun <T : Any?> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) { when (step) { - 0, 3 -> { step++; serializer.serialize(this, value); return } + 0, 3 -> { + step++; serializer.serialize(this, value) + return + } } fail("@$step: encodeSerializableValue($value)") } override fun encodeString(value: String) { when (step) { - 6 -> if (value == "s1") { step++; return } + 6 -> if (value == "s1") { + step++ + return + } } fail("@$step: encodeString($value)") } override fun encodeInt(value: Int) { when (step) { - 8 -> if (value == 42) { step++; return } + 8 -> if (value == 42) { + step++ + return + } } fail("@$step: decodeInt($value)") } override fun endStructure(descriptor: SerialDescriptor) { - when(step) { - 9 -> { checkDataDesc(descriptor); step++; return } - 10 -> { checkContainerDesc(descriptor); step++; return } + when (step) { + 9 -> { + checkDataDesc(descriptor); step++ + return + } + 10 -> { + checkContainerDesc(descriptor); step++ + return + } } fail("@$step: endStructure($descriptor)") } @@ -115,15 +135,17 @@ class SerializationMethodInvocationOrderTest { class Inp : AbstractDecoder() { var step = 0 - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { when (step) { 1 -> { - checkContainerDesc(descriptor); step++; return this + checkContainerDesc(descriptor); step++ + return this } 4 -> { - checkDataDesc(descriptor); step++; return this + checkDataDesc(descriptor); step++ + return this } } fail("@$step: beginStructure($descriptor)") @@ -132,19 +154,24 @@ class SerializationMethodInvocationOrderTest { override fun decodeElementIndex(descriptor: SerialDescriptor): Int { when (step) { 2 -> { - checkContainerDesc(descriptor); step++; return 0 + checkContainerDesc(descriptor); step++ + return 0 } 5 -> { - checkDataDesc(descriptor); step++; return 0 + checkDataDesc(descriptor); step++ + return 0 } 7 -> { - checkDataDesc(descriptor); step++; return 1 + checkDataDesc(descriptor); step++ + return 1 } 9 -> { - checkDataDesc(descriptor); step++; return -1 + checkDataDesc(descriptor); step++ + return -1 } 11 -> { - checkContainerDesc(descriptor); step++; return -1 + checkContainerDesc(descriptor); step++ + return -1 } } fail("@$step: decodeElementIndex($descriptor)") @@ -152,29 +179,44 @@ class SerializationMethodInvocationOrderTest { override fun <T : Any?> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T { when (step) { - 0, 3 -> { step++; return deserializer.deserialize(this) } + 0, 3 -> { + step++ + return deserializer.deserialize(this) + } } fail("@$step: decodeSerializableValue()") } override fun decodeString(): String { when (step) { - 6 -> { step++; return "s1" } + 6 -> { + step++ + return "s1" + } } fail("@$step: decodeString()") } override fun decodeInt(): Int { when (step) { - 8 -> { step++; return 42 } + 8 -> { + step++ + return 42 + } } fail("@$step: decodeInt()") } override fun endStructure(descriptor: SerialDescriptor) { - when(step) { - 10 -> { checkDataDesc(descriptor); step++; return } - 12 -> { checkContainerDesc(descriptor); step++; return } + when (step) { + 10 -> { + checkDataDesc(descriptor); step++ + return + } + 12 -> { + checkContainerDesc(descriptor); step++ + return + } } fail("@$step: endStructure($descriptor)") } diff --git a/core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt b/core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt index 356e9bd6..7e92041e 100644 --- a/core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt +++ b/core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt @@ -198,7 +198,7 @@ class SerializeFlatTest() { class Out(private val name: String) : AbstractEncoder() { var step = 0 - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() override fun beginStructure( descriptor: SerialDescriptor @@ -212,10 +212,12 @@ class SerializeFlatTest() { checkDesc(name, descriptor) when (step) { 1 -> if (index == 0) { - step++; return true + step++ + return true } 3 -> if (index == 1) { - step++; return true + step++ + return true } } fail("@$step: encodeElement($descriptor, $index)") @@ -224,7 +226,8 @@ class SerializeFlatTest() { override fun encodeString(value: String) { when (step) { 2 -> if (value == "s1") { - step++; return + step++ + return } } fail("@$step: encodeString($value)") @@ -232,7 +235,10 @@ class SerializeFlatTest() { override fun encodeInt(value: Int) { when (step) { - 4 -> if (value == 42) { step++; return } + 4 -> if (value == 42) { + step++ + return + } } fail("@$step: decodeInt($value)") } @@ -250,7 +256,7 @@ class SerializeFlatTest() { class Inp(private val name: String) : AbstractDecoder() { var step = 0 - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { checkDesc(name, descriptor) @@ -262,13 +268,16 @@ class SerializeFlatTest() { checkDesc(name, descriptor) when (step) { 1 -> { - step++; return 0 + step++ + return 0 } 3 -> { - step++; return 1 + step++ + return 1 } 5 -> { - step++; return -1 + step++ + return -1 } } fail("@$step: decodeElementIndex($descriptor)") @@ -276,14 +285,20 @@ class SerializeFlatTest() { override fun decodeString(): String { when (step) { - 2 -> { step++; return "s1" } + 2 -> { + step++ + return "s1" + } } fail("@$step: decodeString()") } override fun decodeInt(): Int { when (step) { - 4 -> { step++; return 42 } + 4 -> { + step++ + return 42 + } } fail("@$step: decodeInt()") } diff --git a/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt b/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt index e24c1820..2c91769a 100644 --- a/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt @@ -19,9 +19,9 @@ internal actual inline fun BooleanArray.getChecked(index: Int): Boolean { internal actual fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing { throw SerializationException( - "Serializer for class '${simpleName}' is not found.\n" + - "Mark the class as @Serializable or provide the serializer explicitly.\n" + - "On Kotlin/Native explicitly declared serializer should be used for interfaces and enums without @Serializable annotation" + notRegisteredMessage() + + "To get enum serializer on Kotlin/Native, it should be annotated with @Serializable annotation.\n" + + "To get interface serializer on Kotlin/Native, use PolymorphicSerializer() constructor function.\n" ) } @@ -37,25 +37,36 @@ internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vara else -> null } -@Suppress( - "UNCHECKED_CAST", - "DEPRECATION_ERROR" -) -@OptIn(ExperimentalAssociatedObjects::class) +@Suppress("DEPRECATION_ERROR") 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 for (element in this) result[index++] = element - @Suppress("UNCHECKED_CAST", "USELESS_CAST") + @Suppress("USELESS_CAST") return result as Array<E> } @Suppress("UNCHECKED_CAST") private fun <T> arrayOfAnyNulls(size: Int): Array<T> = arrayOfNulls<Any>(size) as Array<T> -internal actual fun Any.isInstanceOf(kclass: KClass<*>): Boolean = kclass.isInstance(this) - internal actual fun isReferenceArray(rootClass: KClass<Any>): Boolean = rootClass == Array::class diff --git a/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt index 9e51d7f4..2691ce04 100644 --- a/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt +++ b/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt @@ -5,7 +5,5 @@ package kotlinx.serialization.test import kotlinx.serialization.test.Platform -import kotlin.native.concurrent.SharedImmutable -@SharedImmutable public actual val currentPlatform: Platform = Platform.NATIVE diff --git a/core/wasmMain/src/kotlinx/serialization/Serializers.kt b/core/wasmMain/src/kotlinx/serialization/Serializers.kt new file mode 100644 index 00000000..a4993925 --- /dev/null +++ b/core/wasmMain/src/kotlinx/serialization/Serializers.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization + +import kotlin.reflect.* + +@OptIn(ExperimentalAssociatedObjects::class) +@AssociatedObjectKey +@Retention(AnnotationRetention.BINARY) +@PublishedApi +internal annotation class SerializableWith(public val serializer: KClass<out KSerializer<*>>)
\ No newline at end of file diff --git a/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt b/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt new file mode 100644 index 00000000..310df028 --- /dev/null +++ b/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.internal + +import kotlinx.serialization.* +import kotlin.reflect.* + +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun <T> Array<T>.getChecked(index: Int): T { + return get(index) +} + +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun BooleanArray.getChecked(index: Int): Boolean { + return get(index) +} + +internal actual fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing { + throw SerializationException( + "${notRegisteredMessage()}\n" + + "On Kotlin/Wasm explicitly declared serializer should be used for interfaces and enums without @Serializable annotation" + ) +} + +@Suppress( + "UNCHECKED_CAST", + "DEPRECATION_ERROR" +) +@OptIn(ExperimentalAssociatedObjects::class) +internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? = + when (val assocObject = findAssociatedObject<SerializableWith>()) { + is KSerializer<*> -> assocObject as KSerializer<T> + is SerializerFactory -> assocObject.serializer(*args) as KSerializer<T> + else -> null + } + +@Suppress("DEPRECATION_ERROR") +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> = toTypedArray() + +internal actual fun isReferenceArray(rootClass: KClass<Any>): Boolean = rootClass == Array::class
\ No newline at end of file diff --git a/core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt new file mode 100644 index 00000000..fd359b72 --- /dev/null +++ b/core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt @@ -0,0 +1,7 @@ +/* + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.test + +public actual val currentPlatform: Platform = Platform.WASM
\ No newline at end of file |