From 2eb13c4b1a6bb55595035cbf89da602ba87a42fe Mon Sep 17 00:00:00 2001 From: rsinukov Date: Thu, 16 Sep 2021 16:34:56 +0200 Subject: [PATCH 1/5] Serializable meta-annotation Remove @Serializer support --- .../src/kotlinx/serialization/Annotations.kt | 42 ++++++++++++++ .../serialization/MetaSerializableTest.kt | 56 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt index 45b5c1c06d..3193b7b35f 100644 --- a/core/commonMain/src/kotlinx/serialization/Annotations.kt +++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt @@ -73,6 +73,48 @@ 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" + * ``` + * + * @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 + /** * 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..9216652856 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt @@ -0,0 +1,56 @@ +package kotlinx.serialization + +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, AnnotationTarget.PROPERTY) + annotation class MySerializable + + @MetaSerializable + @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) + annotation class MySerializableWithInfo( + val value: Int, + val kclass: KClass<*> + ) + + @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() +// assertNotNull(serializer) + } + + @Test + fun testMetaSerializableWithInfo() { +// val info = serializer().descriptor.annotations.filterIsInstance().first() +// assertEquals(123, info.value) +// assertEquals(String::class, info.kclass) + } + + @Test + fun testMetaSerializableOnProperty() { +// val info = serializer().descriptor +// .getElementAnnotations(0).filterIsInstance().first() +// assertEquals(234, info.value) +// assertEquals(Int::class, info.kclass) + } +} From a1b33b2c2c3df1d64c7ee766c96b01dfa4a9954e Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 4 Jul 2022 12:14:46 +0200 Subject: [PATCH 2/5] Rebase fixes --- core/api/kotlinx-serialization-core.api | 3 ++ .../src/kotlinx/serialization/Annotations.kt | 4 +-- .../serialization/MetaSerializableTest.kt | 29 +++++++++---------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index 53d270941b..63c7dfa608 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -43,6 +43,9 @@ public abstract interface class kotlinx/serialization/KSerializer : kotlinx/seri public abstract fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; } +public abstract interface annotation class kotlinx/serialization/MetaSerializable : java/lang/annotation/Annotation { +} + public final class kotlinx/serialization/MissingFieldException : kotlinx/serialization/SerializationException { public fun (Ljava/lang/String;)V } diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt index 3193b7b35f..c99b6b8cf4 100644 --- a/core/commonMain/src/kotlinx/serialization/Annotations.kt +++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt @@ -75,7 +75,7 @@ public annotation class Serializable( /** * 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 + * Applying [MetaSerializable] to the annotation class A instructs the serialization plugin to treat annotation A * as [Serializable]. * * ``` @@ -91,7 +91,7 @@ public annotation class Serializable( * ``` * * All annotations marked with [MetaSerializable] are saved in the generated [SerialDescriptor] - * as if they are annotated [SerialInfo]. + * as if they are annotated with [SerialInfo]. * * ``` * @MetaSerializable diff --git a/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt index 9216652856..04eded26de 100644 --- a/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt +++ b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt @@ -1,10 +1,9 @@ package kotlinx.serialization +import kotlinx.serialization.test.* import kotlin.reflect.KClass -import kotlin.test.Test +import kotlin.test.* -// TODO: for this test to work, kotlin dependency should be updated -// to serialization plugin with @MetaSerializable support class MetaSerializableTest { @MetaSerializable @@ -34,23 +33,23 @@ class MetaSerializableTest { ) @Test - fun testMetaSerializable() { -// val serializer = serializer() -// assertNotNull(serializer) + fun testMetaSerializable() = noJsLegacy { + val serializer = serializer() + assertNotNull(serializer) } @Test - fun testMetaSerializableWithInfo() { -// val info = serializer().descriptor.annotations.filterIsInstance().first() -// assertEquals(123, info.value) -// assertEquals(String::class, info.kclass) + fun testMetaSerializableWithInfo() = noJsLegacy { + val info = serializer().descriptor.annotations.filterIsInstance().first() + assertEquals(123, info.value) + assertEquals(String::class, info.kclass) } @Test - fun testMetaSerializableOnProperty() { -// val info = serializer().descriptor -// .getElementAnnotations(0).filterIsInstance().first() -// assertEquals(234, info.value) -// assertEquals(Int::class, info.kclass) + fun testMetaSerializableOnProperty() = noJsLegacy { + val info = serializer().descriptor + .getElementAnnotations(0).filterIsInstance().first() + assertEquals(234, info.value) + assertEquals(Int::class, info.kclass) } } From 99481ef5b17f9201b9d2fa1218d6764a302a70c6 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 4 Jul 2022 14:02:37 +0200 Subject: [PATCH 3/5] Add Json-specific test --- .../features/MetaSerializableJsonTest.kt | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 formats/json-tests/commonTest/src/kotlinx/serialization/features/MetaSerializableJsonTest.kt diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/MetaSerializableJsonTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/MetaSerializableJsonTest.kt new file mode 100644 index 0000000000..25efa6b407 --- /dev/null +++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/MetaSerializableJsonTest.kt @@ -0,0 +1,71 @@ +package kotlinx.serialization.features + +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlin.test.* + +class MetaSerializableJsonTest : JsonTestBase() { + @MetaSerializable + @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) + annotation class JsonComment(val comment: String) + + @JsonComment("class_comment") + data class IntDataCommented(val i: Int) + + @Serializable + data class Carrier( + val plain: String, + @JsonComment("string_comment") val commented: StringData, + val intData: IntDataCommented + ) + + class CarrierSerializer : JsonTransformingSerializer(serializer()) { + + private val desc = Carrier.serializer().descriptor + private fun List.comment(): String? = filterIsInstance().firstOrNull()?.comment + + private val commentMap = (0 until desc.elementsCount).associateBy({ desc.getElementName(it) }, + { desc.getElementAnnotations(it).comment() ?: desc.getElementDescriptor(it).annotations.comment() }) + + // NB: we may want to add this to public API + private fun JsonElement.editObject(action: (MutableMap) -> Unit): JsonElement { + val mutable = this.jsonObject.toMutableMap() + action(mutable) + return JsonObject(mutable) + } + + override fun transformDeserialize(element: JsonElement): JsonElement { + return element.editObject { result -> + for ((key, value) in result) { + commentMap[key]?.let { + result[key] = value.editObject { + it.remove("comment") + } + } + } + } + } + + override fun transformSerialize(element: JsonElement): JsonElement { + return element.editObject { result -> + for ((key, value) in result) { + commentMap[key]?.let { comment -> + result[key] = value.editObject { + it["comment"] = JsonPrimitive(comment) + } + } + } + } + } + } + + @Test + fun testMyJsonComment() { + assertJsonFormAndRestored( + CarrierSerializer(), + Carrier("plain", StringData("string1"), IntDataCommented(42)), + """{"plain":"plain","commented":{"data":"string1","comment":"string_comment"},"intData":{"i":42,"comment":"class_comment"}}""" + ) + } + +} From 090ac80b14c4980628cd0abe7a87d2b3abb374d2 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 4 Jul 2022 17:52:37 +0300 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Vsevolod Tolstopyatov --- core/commonMain/src/kotlinx/serialization/Annotations.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt index c99b6b8cf4..09017e56fa 100644 --- a/core/commonMain/src/kotlinx/serialization/Annotations.kt +++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt @@ -74,8 +74,8 @@ public annotation class Serializable( ) /** - * The meta-annotation for adding behaviour of [Serializable] to custom annotations. - * Applying [MetaSerializable] to the annotation class A instructs the serialization plugin to treat annotation A + * 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]. * * ``` From cb3ffad1f170314affdf84a1f7046f694f7c1786 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 4 Jul 2022 16:56:18 +0200 Subject: [PATCH 5/5] remove unnecessary example --- .../src/kotlinx/serialization/Annotations.kt | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt index 09017e56fa..d8864d184d 100644 --- a/core/commonMain/src/kotlinx/serialization/Annotations.kt +++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt @@ -75,22 +75,9 @@ 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]. - * - * ``` - * @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] + * 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]. * * ```