| // WITH_STDLIB |
| // FIR_IDENTICAL |
| |
| // DISABLE_IR_VISIBILITY_CHECKS: ANY |
| // ^ Some functions, calls to which are emitted by the serialization plugin into user code, have internal visibility. |
| // See https://github.com/Kotlin/kotlinx.serialization/issues/2703 |
| |
| // FILE: annotation.kt |
| package kotlinx.serialization |
| |
| import kotlin.annotation.* |
| |
| /* |
| Until the annotation is added to the serialization runtime, |
| we have to create an annotation with that name in the project itself |
| */ |
| @Target(AnnotationTarget.CLASS) |
| @Retention(AnnotationRetention.RUNTIME) |
| annotation class KeepGeneratedSerializer |
| |
| // FILE: main.kt |
| @file:Suppress("OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE") // TODO: support common sources in the test infrastructure |
| |
| import kotlin.jvm.* |
| import kotlin.test.* |
| import kotlinx.serialization.* |
| import kotlinx.serialization.json.* |
| import kotlinx.serialization.encoding.* |
| import kotlinx.serialization.descriptors.* |
| import kotlinx.serialization.builtins.serializer |
| |
| // == value class |
| @Serializable(with = ValueSerializer::class) |
| @KeepGeneratedSerializer |
| @JvmInline |
| value class Value(val i: Int) |
| |
| object ValueSerializer: KSerializer<Value> { |
| override val descriptor = PrimitiveSerialDescriptor("ValueSerializer", PrimitiveKind.INT) |
| override fun deserialize(decoder: Decoder): Value { |
| val value = decoder.decodeInt() |
| return Value(value - 42) |
| } |
| override fun serialize(encoder: Encoder, value: Value) { |
| encoder.encodeInt(value.i + 42) |
| } |
| } |
| |
| // == final class == |
| @Serializable(with = DataSerializer::class) |
| @KeepGeneratedSerializer |
| data class Data(val i: Int) |
| |
| object DataSerializer: KSerializer<Data> { |
| override val descriptor = PrimitiveSerialDescriptor("DataSerializer", PrimitiveKind.INT) |
| override fun deserialize(decoder: Decoder): Data { |
| val value = decoder.decodeInt() |
| return Data(value) |
| } |
| override fun serialize(encoder: Encoder, value: Data) { |
| encoder.encodeInt(value.i) |
| } |
| } |
| |
| // == inheritance == |
| @Serializable(with = ParentSerializer::class) |
| @KeepGeneratedSerializer |
| open class Parent(val p: Int) { |
| override fun equals(other: Any?): Boolean { |
| if (this === other) return true |
| if (other !is Parent) return false |
| |
| if (p != other.p) return false |
| |
| return true |
| } |
| |
| override fun hashCode(): Int { |
| return p |
| } |
| } |
| |
| object ParentSerializer: KSerializer<Parent> { |
| override val descriptor = PrimitiveSerialDescriptor("ParentSerializer", PrimitiveKind.INT) |
| override fun deserialize(decoder: Decoder): Parent { |
| val value = decoder.decodeInt() |
| return Parent(value - 1) |
| } |
| override fun serialize(encoder: Encoder, value: Parent) { |
| encoder.encodeInt(value.p + 1) |
| } |
| } |
| |
| @Serializable |
| data class Child(val c: Int): Parent(0) |
| |
| @Serializable(with = ChildSerializer::class) |
| @KeepGeneratedSerializer |
| data class ChildWithCustom(val c: Int): Parent(0) |
| |
| object ChildSerializer: KSerializer<ChildWithCustom> { |
| override val descriptor = PrimitiveSerialDescriptor("ChildSerializer", PrimitiveKind.INT) |
| override fun deserialize(decoder: Decoder): ChildWithCustom { |
| val value = decoder.decodeInt() |
| return ChildWithCustom(value - 2) |
| } |
| |
| override fun serialize(encoder: Encoder, value: ChildWithCustom) { |
| encoder.encodeInt(value.c + 2) |
| } |
| } |
| |
| // == enums == |
| @Serializable(with = MyEnumSerializer::class) |
| @KeepGeneratedSerializer |
| enum class MyEnum { |
| A, |
| B, |
| FALLBACK |
| } |
| |
| @Serializable |
| data class EnumHolder(val e: MyEnum) |
| |
| object MyEnumSerializer: KSerializer<MyEnum> { |
| val defaultSerializer = MyEnum.generatedSerializer() |
| |
| override val descriptor = PrimitiveSerialDescriptor("MyEnumSerializer", PrimitiveKind.INT) |
| |
| override fun deserialize(decoder: Decoder): MyEnum { |
| decoder.decodeString() |
| return MyEnum.A |
| } |
| |
| override fun serialize(encoder: Encoder, value: MyEnum) { |
| // always encode FALLBACK entry by generated serializer |
| defaultSerializer.serialize(encoder, MyEnum.FALLBACK) |
| } |
| } |
| |
| // == parametrized == |
| @Serializable(with = ParametrizedSerializer::class) |
| @KeepGeneratedSerializer |
| data class ParametrizedData<T>(val t: T) |
| |
| class ParametrizedSerializer(val serializer: KSerializer<Any>): KSerializer<ParametrizedData<Any>> { |
| override val descriptor = PrimitiveSerialDescriptor("ParametrizedSerializer", PrimitiveKind.INT) |
| |
| override fun deserialize(decoder: Decoder): ParametrizedData<Any> { |
| val value = serializer.deserialize(decoder) |
| return ParametrizedData(value) |
| } |
| |
| override fun serialize(encoder: Encoder, value: ParametrizedData<Any>) { |
| serializer.serialize(encoder, value.t) |
| } |
| } |
| |
| // == companion external serializer == |
| @Serializable(WithCompanion.Companion::class) |
| @KeepGeneratedSerializer |
| data class WithCompanion(val value: Int) { |
| @Serializer(WithCompanion::class) |
| companion object { |
| override val descriptor = PrimitiveSerialDescriptor("WithCompanionDesc", PrimitiveKind.INT) |
| override fun deserialize(decoder: Decoder): WithCompanion { |
| val value = decoder.decodeInt() |
| return WithCompanion(value) |
| } |
| |
| override fun serialize(encoder: Encoder, value: WithCompanion) { |
| encoder.encodeInt(value.value) |
| } |
| } |
| } |
| |
| // == serializable object == |
| @Serializable(with = ObjectSerializer::class) |
| @KeepGeneratedSerializer |
| object Object |
| |
| object ObjectSerializer: KSerializer<Object> { |
| override val descriptor = PrimitiveSerialDescriptor("ObjectSerializer", PrimitiveKind.INT) |
| |
| override fun deserialize(decoder: Decoder): Object { |
| decoder.decodeInt() |
| return Object |
| } |
| override fun serialize(encoder: Encoder, value: Object) { |
| encoder.encodeInt(8) |
| } |
| } |
| |
| // == sealed loop == |
| @Serializable |
| public sealed interface SealedInterface |
| |
| @Serializable(with = SealedChild.CustomSerializer::class) |
| @SerialName("child") |
| @KeepGeneratedSerializer |
| data class SealedChild( |
| val child: SealedInterface, |
| ) : SealedInterface { |
| internal object CustomSerializer : KSerializer<SealedChild> by generatedSerializer() |
| } |
| |
| @Serializable |
| public sealed interface Schema |
| |
| @KeepGeneratedSerializer |
| @Serializable(with = Container.CustomSerializer::class) |
| public data class Container( |
| val properties: List<Schema> |
| ) : Schema { |
| internal object CustomSerializer : KSerializer<Container> by generatedSerializer() |
| } |
| |
| @Serializable |
| public sealed interface ParametrizedSchema<T> { |
| val x: T |
| } |
| |
| @KeepGeneratedSerializer |
| @Serializable(with = ParametrizedContainer.CustomSerializer::class) |
| public data class ParametrizedContainer<T>( |
| val properties: List<ParametrizedSchema<T>>, |
| override val x: T |
| ) : ParametrizedSchema<T> { |
| internal object CustomSerializer : KSerializer<ParametrizedContainer<Int>> by Companion.generatedSerializer(Int.serializer()) |
| } |
| |
| fun box(): String = boxWrapper { |
| val value = Value(42) |
| val data = Data(42) |
| val child = Child(1) |
| val param = ParametrizedData<Data>(data) |
| |
| test(Value(1), "43", "1", Value.serializer(), Value.generatedSerializer()) |
| test(Data(2), "2", "{\"i\":2}", Data.serializer(), Data.generatedSerializer()) |
| test(Parent(3), "4", "{\"p\":3}", Parent.serializer(), Parent.generatedSerializer()) |
| test(Child(4), "{\"p\":0,\"c\":4}", "", Child.serializer(), null) |
| test(ChildWithCustom(5), "7", "{\"p\":0,\"c\":5}", ChildWithCustom.serializer(), ChildWithCustom.generatedSerializer()) |
| test(ParametrizedData<Data>(Data(6)), "6", "{\"t\":6}", ParametrizedData.serializer(Data.serializer()), ParametrizedData.generatedSerializer(Data.serializer())) |
| test(WithCompanion(7), "7", "{\"value\":7}", WithCompanion.serializer(), WithCompanion.generatedSerializer()) |
| test(Object, "8", "{}", Object.serializer(), Object.generatedSerializer()) |
| |
| test(MyEnum.A, "\"FALLBACK\"", "\"A\"", MyEnum.serializer(), MyEnum.generatedSerializer()) |
| assertTrue(serializer<MyEnum>() is MyEnumSerializer, "serializer<MyEnum> illegal = " + serializer<MyEnum>()) |
| assertTrue(MyEnum.serializer() is MyEnumSerializer, "MyEnum.serializer() illegal = " + MyEnum.serializer()) |
| assertEquals("kotlinx.serialization.internal.EnumSerializer<MyEnum>", MyEnum.generatedSerializer().toString(), "MyEnum.generatedSerializer() illegal") |
| assertSame(MyEnum.generatedSerializer(), MyEnum.generatedSerializer(), "MyEnum.generatedSerializer() instance differs") |
| |
| assertEquals("Object()", Object.generatedSerializer().descriptor.toString(), "Object.generatedSerializer() illegal") |
| assertSame(Object.generatedSerializer(), Object.generatedSerializer(), "Object.generatedSerializer() instance differs") |
| |
| assertEquals("SealedInterface", SealedInterface.serializer().descriptor.serialName, "SealedInterface.serializer() illegal") |
| |
| assertEquals("Container", Container.serializer().descriptor.serialName, "Container.serializer() illegal") |
| assertEquals("""{"properties":[{"type":"Container","properties":[]}]}""", Json.encodeToString(Container.serializer(), Container(listOf(Container(emptyList()))))) |
| assertEquals("""{"properties":[{"type":"Container","properties":[]}]}""", Json.encodeToString(Container.CustomSerializer, Container(listOf(Container(emptyList()))))) |
| |
| assertEquals("ParametrizedContainer", ParametrizedContainer.serializer(Int.serializer()).descriptor.serialName, "Container.serializer() illegal") |
| assertEquals("""{"properties":[{"type":"ParametrizedContainer","properties":[],"x":1}],"x":2}""", Json.encodeToString(ParametrizedContainer.serializer(Int.serializer()), ParametrizedContainer(listOf(ParametrizedContainer(emptyList(), 1)), 2))) |
| assertEquals("""{"properties":[{"type":"ParametrizedContainer","properties":[],"x":1}],"x":2}""", Json.encodeToString(ParametrizedContainer.CustomSerializer, ParametrizedContainer(listOf(ParametrizedContainer(emptyList(), 1)), 2))) |
| |
| assertFailsWith<MissingFieldException> { |
| // test for fix of https://github.com/Kotlin/kotlinx.serialization/issues/2974 |
| Json.decodeFromString(ParametrizedData.generatedSerializer(Data.serializer()), "{}") |
| } |
| } |
| |
| inline fun <reified T : Any> test( |
| value: T, |
| customJson: String, |
| keepJson: String, |
| serializer: KSerializer<T>, |
| generatedSerializer: KSerializer<T>? |
| ) { |
| val implicitJson = Json.encodeToString(value) |
| assertEquals(customJson, implicitJson, "Json.encodeToString(value: ${T::class.simpleName})") |
| val implicitDecoded = Json.decodeFromString<T>(implicitJson) |
| assertEquals(value, implicitDecoded, "Json.decodeFromString(json): ${T::class.simpleName}") |
| |
| val exlicitJson = Json.encodeToString(serializer, value) |
| assertEquals(customJson, exlicitJson, "Json.encodeToString(${T::class.simpleName}.serializer(), value)") |
| val explicitDecoded = Json.decodeFromString(serializer, exlicitJson) |
| assertEquals(value, explicitDecoded, "Json.decodeFromString(${T::class.simpleName}.serializer(), json)") |
| |
| if (generatedSerializer == null) return |
| val keep = Json.encodeToString(generatedSerializer, value) |
| assertEquals(keepJson, keep, "Json.encodeToString(${T::class.simpleName}.generatedSerializer(), value)") |
| val keepDecoded = Json.decodeFromString(generatedSerializer, keep) |
| assertEquals(value, keepDecoded, "Json.decodeFromString(${T::class.simpleName}.generatedSerializer(), json)") |
| } |
| |
| fun boxWrapper(block: () -> Unit): String { |
| var result = "OK" |
| try { |
| block() |
| } catch (e: Exception) { |
| result = e.message ?: e.toString() |
| } |
| return result |
| } |
| |