summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/api/kotlinx-serialization-core.api116
-rw-r--r--core/build.gradle28
-rw-r--r--core/commonMain/src/kotlinx/serialization/Annotations.kt65
-rw-r--r--core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt2
-rw-r--r--core/commonMain/src/kotlinx/serialization/KSerializer.kt23
-rw-r--r--core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt2
-rw-r--r--core/commonMain/src/kotlinx/serialization/SealedSerializer.kt14
-rw-r--r--core/commonMain/src/kotlinx/serialization/SerialFormat.kt62
-rw-r--r--core/commonMain/src/kotlinx/serialization/SerializationException.kt84
-rw-r--r--core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt135
-rw-r--r--core/commonMain/src/kotlinx/serialization/Serializers.kt322
-rw-r--r--core/commonMain/src/kotlinx/serialization/SerializersCache.kt74
-rw-r--r--core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt60
-rw-r--r--core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt48
-rw-r--r--core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt18
-rw-r--r--core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt7
-rw-r--r--core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt2
-rw-r--r--core/commonMain/src/kotlinx/serialization/encoding/ChunkedDecoder.kt51
-rw-r--r--core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt58
-rw-r--r--core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt43
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt15
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt39
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt10
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Enums.kt73
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt4
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt12
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/NamedCompanion.kt15
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt2
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/NothingSerialDescriptor.kt33
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt3
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt72
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt2
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt1
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt220
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Primitives.kt22
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Tagged.kt28
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Tuples.kt21
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/ValueClasses.kt (renamed from core/commonMain/src/kotlinx/serialization/internal/InlineClasses.kt)8
-rw-r--r--core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt38
-rw-r--r--core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt23
-rw-r--r--core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt26
-rw-r--r--core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt41
-rw-r--r--core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt45
-rw-r--r--core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt52
-rw-r--r--core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt3
-rw-r--r--core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt3
-rw-r--r--core/commonTest/src/kotlinx/serialization/EnumDescriptorsTest.kt101
-rw-r--r--core/commonTest/src/kotlinx/serialization/InheritableSerialInfoTest.kt2
-rw-r--r--core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt56
-rw-r--r--core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt1
-rw-r--r--core/commonTest/src/kotlinx/serialization/PrimitiveSerialDescriptorTest.kt29
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt2
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt7
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt61
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt51
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt100
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt44
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerializersModuleTest.kt135
-rw-r--r--core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt3
-rw-r--r--core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt14
-rw-r--r--core/commonTest/src/kotlinx/serialization/features/SealedInterfacesSerializationTest.kt100
-rw-r--r--core/commonTest/src/kotlinx/serialization/internal/DummySequentialDecoder.kt65
-rw-r--r--core/commonTest/src/kotlinx/serialization/internal/ObjectSerializerTest.kt18
-rw-r--r--core/commonTest/src/kotlinx/serialization/internal/TuplesTest.kt18
-rw-r--r--core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt192
-rw-r--r--core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt7
-rw-r--r--core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt4
-rw-r--r--core/jsMain/src/kotlinx/serialization/SerializersJs.kt (renamed from core/jsMain/src/kotlinx/serialization/Serializers.kt)0
-rw-r--r--core/jsMain/src/kotlinx/serialization/internal/Platform.kt30
-rw-r--r--core/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt7
-rw-r--r--core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt107
-rw-r--r--core/jvmMain/src/kotlinx/serialization/internal/Caching.kt205
-rw-r--r--core/jvmMain/src/kotlinx/serialization/internal/Platform.kt88
-rw-r--r--core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt13
-rw-r--r--core/jvmTest/src/kotlinx/serialization/CachingTest.kt46
-rw-r--r--core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt94
-rw-r--r--core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt37
-rw-r--r--core/nativeMain/src/kotlinx/serialization/internal/Platform.kt33
-rw-r--r--core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt2
-rw-r--r--core/wasmMain/src/kotlinx/serialization/Serializers.kt13
-rw-r--r--core/wasmMain/src/kotlinx/serialization/internal/Platform.kt62
-rw-r--r--core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt7
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