blob: 634c40c12ba1649df44227a7cf02a39e78f58722 [file]
// 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
}