summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeonid Startsev <sandwwraith@users.noreply.github.com>2022-07-07 13:49:35 +0200
committerGitHub <noreply@github.com>2022-07-07 14:49:35 +0300
commitc2327723a8f98fe717c0ec143cff6421b469173e (patch)
treeb92e02de0b8aaf16ce51cc03c300ecbb93c6c888
parentbe99c0d780712b47144be528222377575e9b21ff (diff)
downloadkotlinx.serialization-c2327723a8f98fe717c0ec143cff6421b469173e.tar.gz
Add @MetaSerializable annotation (#1979)
Co-authored-by: rsinukov <rxsinukov@gmail.com> Co-authored-by: Vsevolod Tolstopyatov <qwwdfsad@gmail.com>
-rw-r--r--core/api/kotlinx-serialization-core.api3
-rw-r--r--core/commonMain/src/kotlinx/serialization/Annotations.kt29
-rw-r--r--core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt55
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/MetaSerializableJsonTest.kt71
4 files changed, 158 insertions, 0 deletions
diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api
index 53d27094..63c7dfa6 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 <init> (Ljava/lang/String;)V
}
diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt
index 45b5c1c0..d8864d18 100644
--- a/core/commonMain/src/kotlinx/serialization/Annotations.kt
+++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt
@@ -74,6 +74,35 @@ 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
+ */
+@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.
diff --git a/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt
new file mode 100644
index 00000000..04eded26
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt
@@ -0,0 +1,55 @@
+package kotlinx.serialization
+
+import kotlinx.serialization.test.*
+import kotlin.reflect.KClass
+import kotlin.test.*
+
+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() = noJsLegacy {
+ val serializer = serializer<Project1>()
+ assertNotNull(serializer)
+ }
+
+ @Test
+ fun testMetaSerializableWithInfo() = noJsLegacy {
+ val info = serializer<Project2>().descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first()
+ assertEquals(123, info.value)
+ assertEquals(String::class, info.kclass)
+ }
+
+ @Test
+ fun testMetaSerializableOnProperty() = noJsLegacy {
+ val info = serializer<Wrapper>().descriptor
+ .getElementAnnotations(0).filterIsInstance<MySerializableWithInfo>().first()
+ assertEquals(234, info.value)
+ assertEquals(Int::class, info.kclass)
+ }
+}
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 00000000..25efa6b4
--- /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<Carrier>(serializer()) {
+
+ private val desc = Carrier.serializer().descriptor
+ private fun List<Annotation>.comment(): String? = filterIsInstance<JsonComment>().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<String, JsonElement>) -> 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"}}"""
+ )
+ }
+
+}