From 0edaff7f076c1610d324a762faca0145773484ce Mon Sep 17 00:00:00 2001 From: rsinukov Date: Thu, 16 Sep 2021 16:34:56 +0200 Subject: [PATCH 1/2] Serializable meta-anootation --- .../src/kotlinx/serialization/Annotations.kt | 61 +++++++++++++++++++ .../serialization/MetaSerializableTest.kt | 57 +++++++++++++++++ guide/test/MySerializable.kt | 57 +++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt create mode 100644 guide/test/MySerializable.kt diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt index b7710a90e5..a6843f4288 100644 --- a/core/commonMain/src/kotlinx/serialization/Annotations.kt +++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt @@ -73,6 +73,67 @@ public annotation class Serializable( val with: KClass> = KSerializer::class // Default value indicates that auto-generated serializer is used ) +/** + * The meta-annotation for adding behaviour of [Serializable] to custom annotations. + * Applying [MetaSerializable] to the annotation class instructs the serialization plugin to treat this annotation + * as [Serializable]. + * + * ``` + * @MetaSerializable + * @Target(AnnotationTarget.CLASS) + * annotation class MySerializable + * + * @MySerializable + * class MyData(val myData: AnotherData, val intProperty: Int, ...) + * + * // Produces JSON string using the generated serializer + * val jsonString = Json.encodeToJson(MyData.serializer(), instance) + * ``` + * + * All annotations marked with [MetaSerializable] are saved in the generated [SerialDescriptor] + * as if they are annotated [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().first().data // <- returns "some_data" + * ``` + * + * Additionally, the user-defined serializer can be specified using parameter + * annotated with [MetaSerializable.Serializer]: + * ``` + * @MetaSerializable + * @Target(AnnotationTarget.CLASS) + * annotation class MySerializable( + * @MetaSerializable.Serializer val serializer: KClass>, + * val data: String + * ) + * + * @MetaSerializable(serializer = MyDataCustomSerializer::class, data = "some_data") + * class MyData(...) + * + * MyData.serializer() // <- returns MyDataCustomSerializer + * ``` + * + * @see Serializable + * @see SerialInfo + * @see UseSerializers + * @see Serializer + */ +@Target(AnnotationTarget.ANNOTATION_CLASS) +//@Retention(AnnotationRetention.RUNTIME) // Runtime is the default retention, also see KT-41082 +@ExperimentalSerializationApi +public annotation class MetaSerializable { + @Target(AnnotationTarget.PROPERTY) + public annotation class Serializer +} + /** * 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, diff --git a/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt new file mode 100644 index 0000000000..94ba45abb6 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt @@ -0,0 +1,57 @@ +package kotlinx.serialization + +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.reflect.KClass +import kotlin.test.Test + +// TODO: for this test to work, kotlin dependency should be updated +// to serialization plugin with @MetaSerializable support +class MetaSerializableTest { + + @MetaSerializable + @Target(AnnotationTarget.CLASS) + annotation class MySerializableWithCustomSerializer(@MetaSerializable.Serializer val with: KClass> = KSerializer::class) + + @MetaSerializable + @Target(AnnotationTarget.CLASS) + annotation class MySerializableWithInfo(val value: Int, val klass: KClass<*>) + + @MySerializableWithCustomSerializer(MySerializer::class) + class Project1(val name: String, val language: String) + + @MySerializableWithCustomSerializer + class Project2(val name: String, val language: String) + + @MySerializableWithInfo(value = 123, String::class) + class Project3(val name: String, val language: String) + + object MySerializer : KSerializer { + override val descriptor: SerialDescriptor + get() = throw NotImplementedError() + + override fun serialize(encoder: Encoder, value: Project1) = throw NotImplementedError() + override fun deserialize(decoder: Decoder): Project1 = throw NotImplementedError() + } + + @Test + fun testCustomSerializer() { +// val serializer = serializer() +// assertEquals(serializer, MySerializer) + } + + @Test + fun testDefaultSerializer() { +// val serializer = serializer() +// assertNotNull(serializer) + } + + @Test + fun testDefaultSerialInfo() { +// val descriptor = serializer().descriptor +// val annotation = descriptor.annotations.filterIsInstance().first() +// assertEquals(123, annotation.value) +// assertEquals(String::class, annotation.klass) + } +} diff --git a/guide/test/MySerializable.kt b/guide/test/MySerializable.kt new file mode 100644 index 0000000000..8af6c6d85e --- /dev/null +++ b/guide/test/MySerializable.kt @@ -0,0 +1,57 @@ +import kotlinx.serialization.KSerializer +import kotlinx.serialization.MetaSerializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.reflect.KClass +import kotlin.test.Test + +// TODO: for this test to work, kotlin dependency should be updated +// to serialization plugin with @MetaSerializable support + +@MetaSerializable +@Target(AnnotationTarget.CLASS) +annotation class MySerializable( + @MetaSerializable.Serializer val with: KClass> = KSerializer::class, +) + +@MySerializable(MySerializer::class) +class Project1(val name: String, val language: String) + +@MySerializable +class Project2(val name: String, val language: String) + +object MySerializer : KSerializer { + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Project", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Project1) = encoder.encodeString("${value.name}:${value.language}") + + override fun deserialize(decoder: Decoder): Project1 { + val params = decoder.decodeString().split(':') + return Project1(params[0], params[1]) + } +} + +class Test { + + @Test + fun testCustomSerializer() { +// val string = Json.encodeToString(serializer(), Project1("name", "lang")) +// assertEquals("\"name:lang\"", string) +// val reconstructed = Json.decodeFromString(serializer(), string) +// assertEquals("name", reconstructed.name) +// assertEquals("lang", reconstructed.language) + } + + @Test + fun testDefaultSerializer() { +// val string = Json.encodeToString(serializer(), Project2("name", "lang")) +// assertEquals("""{"name":"name","language":"lang"}""", string) +// val reconstructed = Json.decodeFromString(serializer(), string) +// assertEquals("name", reconstructed.name) +// assertEquals("lang", reconstructed.language) + } +} From 29806d07da66f4f67dd8c7732143bfc95992904d Mon Sep 17 00:00:00 2001 From: rsinukov Date: Mon, 20 Dec 2021 16:37:33 +0100 Subject: [PATCH 2/2] Remove @Serializer support --- .../src/kotlinx/serialization/Annotations.kt | 21 +------ .../serialization/MetaSerializableTest.kt | 53 +++++++++-------- guide/test/MySerializable.kt | 57 ------------------- 3 files changed, 27 insertions(+), 104 deletions(-) delete mode 100644 guide/test/MySerializable.kt diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt index a6843f4288..c23244d4ba 100644 --- a/core/commonMain/src/kotlinx/serialization/Annotations.kt +++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt @@ -105,22 +105,6 @@ public annotation class Serializable( * serializer.descriptor.annotations.filterIsInstance().first().data // <- returns "some_data" * ``` * - * Additionally, the user-defined serializer can be specified using parameter - * annotated with [MetaSerializable.Serializer]: - * ``` - * @MetaSerializable - * @Target(AnnotationTarget.CLASS) - * annotation class MySerializable( - * @MetaSerializable.Serializer val serializer: KClass>, - * val data: String - * ) - * - * @MetaSerializable(serializer = MyDataCustomSerializer::class, data = "some_data") - * class MyData(...) - * - * MyData.serializer() // <- returns MyDataCustomSerializer - * ``` - * * @see Serializable * @see SerialInfo * @see UseSerializers @@ -129,10 +113,7 @@ public annotation class Serializable( @Target(AnnotationTarget.ANNOTATION_CLASS) //@Retention(AnnotationRetention.RUNTIME) // Runtime is the default retention, also see KT-41082 @ExperimentalSerializationApi -public annotation class MetaSerializable { - @Target(AnnotationTarget.PROPERTY) - public annotation class Serializer -} +public annotation class MetaSerializable /** * Instructs the serialization plugin to turn this class into serializer for specified class [forClass]. diff --git a/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt index 94ba45abb6..9216652856 100644 --- a/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt +++ b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt @@ -1,8 +1,5 @@ package kotlinx.serialization -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import kotlin.reflect.KClass import kotlin.test.Test @@ -11,47 +8,49 @@ import kotlin.test.Test class MetaSerializableTest { @MetaSerializable - @Target(AnnotationTarget.CLASS) - annotation class MySerializableWithCustomSerializer(@MetaSerializable.Serializer val with: KClass> = KSerializer::class) + @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) + annotation class MySerializable @MetaSerializable - @Target(AnnotationTarget.CLASS) - annotation class MySerializableWithInfo(val value: Int, val klass: KClass<*>) + @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) + annotation class MySerializableWithInfo( + val value: Int, + val kclass: KClass<*> + ) - @MySerializableWithCustomSerializer(MySerializer::class) + @MySerializable class Project1(val name: String, val language: String) - @MySerializableWithCustomSerializer + @MySerializableWithInfo(123, String::class) class Project2(val name: String, val language: String) - @MySerializableWithInfo(value = 123, String::class) + @MySerializableWithInfo(123, String::class) + @Serializable class Project3(val name: String, val language: String) - object MySerializer : KSerializer { - override val descriptor: SerialDescriptor - get() = throw NotImplementedError() - - override fun serialize(encoder: Encoder, value: Project1) = throw NotImplementedError() - override fun deserialize(decoder: Decoder): Project1 = throw NotImplementedError() - } + @Serializable + class Wrapper( + @MySerializableWithInfo(234, Int::class) val project: Project3 + ) @Test - fun testCustomSerializer() { + fun testMetaSerializable() { // val serializer = serializer() -// assertEquals(serializer, MySerializer) +// assertNotNull(serializer) } @Test - fun testDefaultSerializer() { -// val serializer = serializer() -// assertNotNull(serializer) + fun testMetaSerializableWithInfo() { +// val info = serializer().descriptor.annotations.filterIsInstance().first() +// assertEquals(123, info.value) +// assertEquals(String::class, info.kclass) } @Test - fun testDefaultSerialInfo() { -// val descriptor = serializer().descriptor -// val annotation = descriptor.annotations.filterIsInstance().first() -// assertEquals(123, annotation.value) -// assertEquals(String::class, annotation.klass) + fun testMetaSerializableOnProperty() { +// val info = serializer().descriptor +// .getElementAnnotations(0).filterIsInstance().first() +// assertEquals(234, info.value) +// assertEquals(Int::class, info.kclass) } } diff --git a/guide/test/MySerializable.kt b/guide/test/MySerializable.kt deleted file mode 100644 index 8af6c6d85e..0000000000 --- a/guide/test/MySerializable.kt +++ /dev/null @@ -1,57 +0,0 @@ -import kotlinx.serialization.KSerializer -import kotlinx.serialization.MetaSerializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlin.reflect.KClass -import kotlin.test.Test - -// TODO: for this test to work, kotlin dependency should be updated -// to serialization plugin with @MetaSerializable support - -@MetaSerializable -@Target(AnnotationTarget.CLASS) -annotation class MySerializable( - @MetaSerializable.Serializer val with: KClass> = KSerializer::class, -) - -@MySerializable(MySerializer::class) -class Project1(val name: String, val language: String) - -@MySerializable -class Project2(val name: String, val language: String) - -object MySerializer : KSerializer { - - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Project", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: Project1) = encoder.encodeString("${value.name}:${value.language}") - - override fun deserialize(decoder: Decoder): Project1 { - val params = decoder.decodeString().split(':') - return Project1(params[0], params[1]) - } -} - -class Test { - - @Test - fun testCustomSerializer() { -// val string = Json.encodeToString(serializer(), Project1("name", "lang")) -// assertEquals("\"name:lang\"", string) -// val reconstructed = Json.decodeFromString(serializer(), string) -// assertEquals("name", reconstructed.name) -// assertEquals("lang", reconstructed.language) - } - - @Test - fun testDefaultSerializer() { -// val string = Json.encodeToString(serializer(), Project2("name", "lang")) -// assertEquals("""{"name":"name","language":"lang"}""", string) -// val reconstructed = Json.decodeFromString(serializer(), string) -// assertEquals("name", reconstructed.name) -// assertEquals("lang", reconstructed.language) - } -}