From 87ee5fff02e1d21507618a9eda2a13b239deab70 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 23 May 2021 18:59:35 +0200 Subject: [PATCH 01/13] Add Kotlin serializer and basic test --- .../kotlin/serialization/KotlinSerializer.kt | 177 ++++++++++++++++++ .../serialization/KotlinSerializerTest.kt | 91 +++++++++ pom.xml | 16 +- 3 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt create mode 100644 kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt new file mode 100644 index 00000000..35094d2f --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2010-2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.axonframework.extensions.kotlin.serialization + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import org.axonframework.common.ObjectUtils +import org.axonframework.serialization.AnnotationRevisionResolver +import org.axonframework.serialization.ChainingConverter +import org.axonframework.serialization.Converter +import org.axonframework.serialization.RevisionResolver +import org.axonframework.serialization.SerializedObject +import org.axonframework.serialization.SerializedType +import org.axonframework.serialization.Serializer +import org.axonframework.serialization.SimpleSerializedObject +import org.axonframework.serialization.SimpleSerializedType +import org.axonframework.serialization.UnknownSerializedType +import kotlin.reflect.KClass +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.companionObjectInstance + +/** + * Implementation of Axon Serializer that uses a kotlinx.serialization implementation. + * The serialized format is JSON. + * + * @see kotlinx.serialization.Serializer + * @see org.axonframework.serialization.Serializer + */ +class KotlinSerializer( + private val revisionResolver: RevisionResolver, + private val converter: Converter, + private val json: Json +) : Serializer { + + private val serializerCache: MutableMap, KSerializer<*>> = mutableMapOf() + + override fun serialize(`object`: Any?, expectedRepresentation: Class): SerializedObject { + try { + val type = ObjectUtils.nullSafeTypeOf(`object`) + val serializer: KSerializer = type.serializer() + return when { + expectedRepresentation.isAssignableFrom(String::class.java) -> + SimpleSerializedObject( + json.encodeToString(serializer, `object`) as T, + expectedRepresentation, + typeForClass(type) + ) + + expectedRepresentation.isAssignableFrom(JsonElement::class.java) -> + SimpleSerializedObject( + json.encodeToJsonElement(serializer, `object`) as T, + expectedRepresentation, + typeForClass(type) + ) + + else -> + throw org.axonframework.serialization.SerializationException("Cannot serialize type $type to representation $expectedRepresentation. String and JsonElement are supported.") + } + } catch (ex: SerializationException) { + throw org.axonframework.serialization.SerializationException("Cannot serialize type ${`object`?.javaClass?.name} to representation $expectedRepresentation.", ex) + } + } + + override fun canSerializeTo(expectedRepresentation: Class): Boolean = + expectedRepresentation == String::class.java || + expectedRepresentation == JsonElement::class.java + + override fun deserialize(serializedObject: SerializedObject?): T? { + try { + if (serializedObject == null) { + return null + } + + if (serializedObject.type == SerializedType.emptyType()) { + return null + } + + val serializer: KSerializer = serializedObject.serializer() + return when { + serializedObject.contentType.isAssignableFrom(String::class.java) -> + json.decodeFromString(serializer, serializedObject.data as String) + + serializedObject.contentType.isAssignableFrom(JsonElement::class.java) -> + json.decodeFromJsonElement(serializer, serializedObject.data as JsonElement) + + else -> + throw org.axonframework.serialization.SerializationException("Cannot deserialize from content type ${serializedObject.contentType} to type ${serializedObject.type}. String and JsonElement are supported.") + } + } catch (ex: SerializationException) { + throw org.axonframework.serialization.SerializationException( + "Could not deserialize from content type ${serializedObject?.contentType} to type ${serializedObject?.type}", + ex + ) + } + } + + private fun SerializedObject.serializer(): KSerializer = + classForType(type).serializer() as KSerializer + + /** + * When a type is compiled by the Kotlin compiler extension, a companion object + * is created which contains a method `serializer()`. This method should be called + * to get the serializer of the class. + * + * In a 'normal' serialization environment, you would call the MyClass.serializer() + * method directly. Here we are in a generic setting, and need reflection to call + * the method. + * + * This method caches the reflection mapping from class to serializer for efficiency. + */ + private fun Class.serializer(): KSerializer = + serializerCache.computeIfAbsent(this) { + // Class: T must be non-null + val kClass = (this as Class).kotlin + + val companion = kClass.companionObject + ?: throw SerializationException("Class $this has no companion object. Did you mark it as @Serializable?") + + val serializerMethod = companion.java.getMethod("serializer") + ?: throw SerializationException("Class $this has no serializer() method. Did you mark it as @Serializable?") + + serializerMethod.invoke(kClass.companionObjectInstance) as KSerializer<*> + } as KSerializer + + override fun classForType(type: SerializedType): Class<*> = + if (SerializedType.emptyType() == type) { + Void.TYPE + } else { + try { + Class.forName(type.name) + } catch (e: ClassNotFoundException) { + UnknownSerializedType::class.java + } + } + + override fun typeForClass(type: Class<*>): SerializedType = + SimpleSerializedType(type.name, revisionOf(type)) + + private fun revisionOf(type: Class<*>): String? = + revisionResolver.revisionOf(type) + + override fun getConverter(): Converter = + converter + +} + +class KotlinSerializerConfiguration { + var revisionResolver: RevisionResolver = AnnotationRevisionResolver() + var converter: Converter = ChainingConverter() + var json: Json = Json +} + +fun kotlinSerializer(init: KotlinSerializerConfiguration.() -> Unit = {}): KotlinSerializer { + val configuration = KotlinSerializerConfiguration() + configuration.init() + return KotlinSerializer( + configuration.revisionResolver, + configuration.converter, + configuration.json, + ) +} \ No newline at end of file diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt new file mode 100644 index 00000000..7337f132 --- /dev/null +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt @@ -0,0 +1,91 @@ +package org.axonframework.extensions.kotlin.serialization + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import org.axonframework.serialization.AnnotationRevisionResolver +import org.axonframework.serialization.ChainingConverter +import org.axonframework.serialization.SerializedType +import org.axonframework.serialization.SimpleSerializedObject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class KotlinSerializerTest { + + /** + * This class will automatically become serializable through the Kotlin serialization compiler plugin. + */ + @Serializable + data class TestData( + val name: String, + val value: Float? + ) + + @Test + fun canSerializeTo() { + val serializer = kotlinSerializer() + + assertTrue(serializer.canSerializeTo(String::class.java)) + assertTrue(serializer.canSerializeTo(JsonElement::class.java)) + } + + @Test + fun `configuration options`() { + val serializer = kotlinSerializer { + json = Json + converter = ChainingConverter() + revisionResolver = AnnotationRevisionResolver() + } + assertNotNull(serializer) + } + + @Test + fun serialize() { + val serializer = kotlinSerializer() + + val emptySerialized = serializer.serialize(TestData("", null), String::class.java) + assertEquals("SimpleSerializedType[org.axonframework.extensions.kotlin.serialization.KotlinSerializerTest\$TestData] (revision null)", emptySerialized.type.toString()) + assertEquals("""{"name":"","value":null}""", emptySerialized.data) + assertEquals(String::class.java, emptySerialized.contentType) + + val filledSerialized = serializer.serialize(TestData("name", 1.23f), String::class.java) + assertEquals("SimpleSerializedType[org.axonframework.extensions.kotlin.serialization.KotlinSerializerTest\$TestData] (revision null)", filledSerialized.type.toString()) + assertEquals("""{"name":"name","value":1.23}""", filledSerialized.data) + assertEquals(String::class.java, filledSerialized.contentType) + } + + @Test + fun deserialize() { + val serializer = kotlinSerializer() + + val nullDeserialized: Any? = serializer.deserialize(SimpleSerializedObject( + "", + String::class.java, + SerializedType.emptyType() + )) + assertNull(nullDeserialized) + + val emptyDeserialized: Any? = serializer.deserialize(SimpleSerializedObject( + """{"name":"","value":null}""", + String::class.java, + TestData::class.java.name, + null + )) + assertNotNull(emptyDeserialized as TestData) + assertEquals(emptyDeserialized.name, "") + assertEquals(emptyDeserialized.value, null) + + val filledDeserialized: Any? = serializer.deserialize(SimpleSerializedObject( + """{"name":"name","value":1.23}""", + String::class.java, + TestData::class.java.name, + null + )) + assertNotNull(filledDeserialized as TestData) + assertEquals(filledDeserialized.name, "name") + assertEquals(filledDeserialized.value, 1.23f) + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 319c361e..c8438252 100644 --- a/pom.xml +++ b/pom.xml @@ -34,8 +34,9 @@ 4.5 UTF-8 - 1.4.32 + 1.5.0 2.0.6 + 1.2.1 2.12.3 1.11.0 5.7.2 @@ -130,6 +131,13 @@ test + + + org.jetbrains.kotlinx + kotlinx-serialization-json + ${kotlin-serialization.version} + + org.junit.jupiter @@ -434,6 +442,7 @@ no-arg all-open + kotlinx-serialization @@ -477,6 +486,11 @@ kotlin-maven-noarg ${kotlin.version} + + org.jetbrains.kotlin + kotlin-maven-serialization + ${kotlin.version} + From 43c5d0e8e8167f65c0e61ba48a8e47cc59b1dc19 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 30 May 2021 16:46:12 +0200 Subject: [PATCH 02/13] Comments: - Dependencies with optional mark - Add test case (and code fixes) for nullable serializer code - 2021 license --- .../kotlin/serialization/KotlinSerializer.kt | 48 +++++++++++++++---- .../serialization/KotlinSerializerTest.kt | 4 ++ pom.xml | 2 + 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index 35094d2f..ec81abab 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2020. Axon Framework + * Copyright (c) 2010-2021. Axon Framework * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull import org.axonframework.common.ObjectUtils import org.axonframework.serialization.AnnotationRevisionResolver import org.axonframework.serialization.ChainingConverter @@ -31,7 +32,6 @@ import org.axonframework.serialization.Serializer import org.axonframework.serialization.SimpleSerializedObject import org.axonframework.serialization.SimpleSerializedType import org.axonframework.serialization.UnknownSerializedType -import kotlin.reflect.KClass import kotlin.reflect.full.companionObject import kotlin.reflect.full.companionObjectInstance @@ -39,8 +39,12 @@ import kotlin.reflect.full.companionObjectInstance * Implementation of Axon Serializer that uses a kotlinx.serialization implementation. * The serialized format is JSON. * + * The DSL function kotlinSerializer can be used to easily configure the parameters + * for this serializer. + * * @see kotlinx.serialization.Serializer * @see org.axonframework.serialization.Serializer + * @see kotlinSerializer */ class KotlinSerializer( private val revisionResolver: RevisionResolver, @@ -50,21 +54,20 @@ class KotlinSerializer( private val serializerCache: MutableMap, KSerializer<*>> = mutableMapOf() - override fun serialize(`object`: Any?, expectedRepresentation: Class): SerializedObject { + override fun serialize(value: Any?, expectedRepresentation: Class): SerializedObject { try { - val type = ObjectUtils.nullSafeTypeOf(`object`) - val serializer: KSerializer = type.serializer() + val type = ObjectUtils.nullSafeTypeOf(value) return when { expectedRepresentation.isAssignableFrom(String::class.java) -> SimpleSerializedObject( - json.encodeToString(serializer, `object`) as T, + (if (value == null) "null" else json.encodeToString(type.serializer(), value)) as T, expectedRepresentation, typeForClass(type) ) expectedRepresentation.isAssignableFrom(JsonElement::class.java) -> SimpleSerializedObject( - json.encodeToJsonElement(serializer, `object`) as T, + (if (value == null) JsonNull else json.encodeToJsonElement(type.serializer(), value)) as T, expectedRepresentation, typeForClass(type) ) @@ -73,7 +76,7 @@ class KotlinSerializer( throw org.axonframework.serialization.SerializationException("Cannot serialize type $type to representation $expectedRepresentation. String and JsonElement are supported.") } } catch (ex: SerializationException) { - throw org.axonframework.serialization.SerializationException("Cannot serialize type ${`object`?.javaClass?.name} to representation $expectedRepresentation.", ex) + throw org.axonframework.serialization.SerializationException("Cannot serialize type ${value?.javaClass?.name} to representation $expectedRepresentation.", ex) } } @@ -149,8 +152,12 @@ class KotlinSerializer( } } - override fun typeForClass(type: Class<*>): SerializedType = - SimpleSerializedType(type.name, revisionOf(type)) + override fun typeForClass(type: Class<*>?): SerializedType = + if (type == null || Void.TYPE == type || Void::class.java == type) { + SimpleSerializedType.emptyType() + } else { + SimpleSerializedType(type.name, revisionResolver.revisionOf(type)) + } private fun revisionOf(type: Class<*>): String? = revisionResolver.revisionOf(type) @@ -160,12 +167,33 @@ class KotlinSerializer( } +/** + * Configuration which will be used to construct a KotlinSerializer. + * This class is used in the kotlinSerializer DSL function. + * + * @see KotlinSerializer + * @see kotlinSerializer + */ class KotlinSerializerConfiguration { var revisionResolver: RevisionResolver = AnnotationRevisionResolver() var converter: Converter = ChainingConverter() var json: Json = Json } +/** + * DSL function to configure a new KotlinSerializer. + * + * Usage example: + * + * val serializer: KotlinSerializer = kotlinSerializer { + * json = Json + * converter = ChainingConverter() + * revisionResolver = AnnotationRevisionResolver() + * } + * + * + * @see KotlinSerializer + */ fun kotlinSerializer(init: KotlinSerializerConfiguration.() -> Unit = {}): KotlinSerializer { val configuration = KotlinSerializerConfiguration() configuration.init() diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt index 7337f132..1843acb9 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt @@ -55,6 +55,10 @@ class KotlinSerializerTest { assertEquals("SimpleSerializedType[org.axonframework.extensions.kotlin.serialization.KotlinSerializerTest\$TestData] (revision null)", filledSerialized.type.toString()) assertEquals("""{"name":"name","value":1.23}""", filledSerialized.data) assertEquals(String::class.java, filledSerialized.contentType) + + val nullSerialized = serializer.serialize(null, String::class.java) + assertEquals("null", nullSerialized.data) + assertEquals(String::class.java, nullSerialized.contentType) } @Test diff --git a/pom.xml b/pom.xml index c8438252..de1967c7 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,7 @@ org.jetbrains.kotlinx kotlinx-serialization-json ${kotlin-serialization.version} + true @@ -490,6 +491,7 @@ org.jetbrains.kotlin kotlin-maven-serialization ${kotlin.version} + true From 2b7735b8b85f568b2c02eec3469576cb9be28aa4 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Thu, 3 Jun 2021 19:42:22 +0200 Subject: [PATCH 03/13] Add defaults --- .../extensions/kotlin/serialization/KotlinSerializer.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index ec81abab..d63e8c01 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -47,8 +47,8 @@ import kotlin.reflect.full.companionObjectInstance * @see kotlinSerializer */ class KotlinSerializer( - private val revisionResolver: RevisionResolver, - private val converter: Converter, + private val revisionResolver: RevisionResolver = AnnotationRevisionResolver(), + private val converter: Converter = ChainingConverter(), private val json: Json ) : Serializer { From 1256d2d65a2425ab1a8958054473705de6ee7a0e Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Thu, 3 Jun 2021 19:42:28 +0200 Subject: [PATCH 04/13] Add since and author --- .../extensions/kotlin/serialization/KotlinSerializer.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index d63e8c01..6da155fc 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -45,6 +45,9 @@ import kotlin.reflect.full.companionObjectInstance * @see kotlinx.serialization.Serializer * @see org.axonframework.serialization.Serializer * @see kotlinSerializer + * + * @since 0.2.0 + * @author Hidde Wieringa */ class KotlinSerializer( private val revisionResolver: RevisionResolver = AnnotationRevisionResolver(), From f7d4ee9f6cc1f1d91f8dd3e2ba3b831162dc6e48 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Thu, 3 Jun 2021 19:53:01 +0200 Subject: [PATCH 05/13] Handle unknown serialized type --- .../kotlin/serialization/KotlinSerializer.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index 6da155fc..69383740 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -34,6 +34,7 @@ import org.axonframework.serialization.SimpleSerializedType import org.axonframework.serialization.UnknownSerializedType import kotlin.reflect.full.companionObject import kotlin.reflect.full.companionObjectInstance +import org.axonframework.serialization.SerializationException as AxonSerializationException /** * Implementation of Axon Serializer that uses a kotlinx.serialization implementation. @@ -76,10 +77,10 @@ class KotlinSerializer( ) else -> - throw org.axonframework.serialization.SerializationException("Cannot serialize type $type to representation $expectedRepresentation. String and JsonElement are supported.") + throw AxonSerializationException("Cannot serialize type $type to representation $expectedRepresentation. String and JsonElement are supported.") } } catch (ex: SerializationException) { - throw org.axonframework.serialization.SerializationException("Cannot serialize type ${value?.javaClass?.name} to representation $expectedRepresentation.", ex) + throw AxonSerializationException("Cannot serialize type ${value?.javaClass?.name} to representation $expectedRepresentation.", ex) } } @@ -97,7 +98,12 @@ class KotlinSerializer( return null } - val serializer: KSerializer = serializedObject.serializer() + val foundType = classForType(serializedObject.type) + if (UnknownSerializedType::class.java.isAssignableFrom(foundType)) { + return UnknownSerializedType(this, serializedObject) as T + } + + val serializer: KSerializer = foundType.serializer() as KSerializer return when { serializedObject.contentType.isAssignableFrom(String::class.java) -> json.decodeFromString(serializer, serializedObject.data as String) @@ -106,10 +112,10 @@ class KotlinSerializer( json.decodeFromJsonElement(serializer, serializedObject.data as JsonElement) else -> - throw org.axonframework.serialization.SerializationException("Cannot deserialize from content type ${serializedObject.contentType} to type ${serializedObject.type}. String and JsonElement are supported.") + throw AxonSerializationException("Cannot deserialize from content type ${serializedObject.contentType} to type ${serializedObject.type}. String and JsonElement are supported.") } } catch (ex: SerializationException) { - throw org.axonframework.serialization.SerializationException( + throw AxonSerializationException( "Could not deserialize from content type ${serializedObject?.contentType} to type ${serializedObject?.type}", ex ) From 798b3fd031354e2a46fbedb2b378bbf3694d14f4 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Thu, 3 Jun 2021 20:48:20 +0200 Subject: [PATCH 06/13] Custom serializer --- .../kotlin/serialization/KotlinSerializer.kt | 12 +-- .../serializer/ConfigTokenSerializer.kt | 49 +++++++++++++ .../serialization/KotlinSerializerTest.kt | 73 ++++++++++++++----- 3 files changed, 112 insertions(+), 22 deletions(-) create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/serializer/ConfigTokenSerializer.kt diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index 69383740..085798a2 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -16,6 +16,7 @@ package org.axonframework.extensions.kotlin.serialization +import kotlinx.serialization.ContextualSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json @@ -134,6 +135,10 @@ class KotlinSerializer( * method directly. Here we are in a generic setting, and need reflection to call * the method. * + * If there is no `serializer` method, a ContextualSerializer will be created. This + * serializer requires manual configuration of the SerializersModule containing a + * KSerializer which will be used when this class is serialized. + * * This method caches the reflection mapping from class to serializer for efficiency. */ private fun Class.serializer(): KSerializer = @@ -142,10 +147,10 @@ class KotlinSerializer( val kClass = (this as Class).kotlin val companion = kClass.companionObject - ?: throw SerializationException("Class $this has no companion object. Did you mark it as @Serializable?") + ?: return@computeIfAbsent ContextualSerializer(kClass) val serializerMethod = companion.java.getMethod("serializer") - ?: throw SerializationException("Class $this has no serializer() method. Did you mark it as @Serializable?") + ?: return@computeIfAbsent ContextualSerializer(kClass) serializerMethod.invoke(kClass.companionObjectInstance) as KSerializer<*> } as KSerializer @@ -168,9 +173,6 @@ class KotlinSerializer( SimpleSerializedType(type.name, revisionResolver.revisionOf(type)) } - private fun revisionOf(type: Class<*>): String? = - revisionResolver.revisionOf(type) - override fun getConverter(): Converter = converter diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/serializer/ConfigTokenSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/serializer/ConfigTokenSerializer.kt new file mode 100644 index 00000000..2aa28289 --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/serializer/ConfigTokenSerializer.kt @@ -0,0 +1,49 @@ +package org.axonframework.extensions.kotlin.serialization.serializer + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.Serializer +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.CompositeDecoder.Companion.DECODE_DONE +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.encoding.encodeStructure +import org.axonframework.eventhandling.tokenstore.ConfigToken + +@OptIn(ExperimentalSerializationApi::class) +@Serializer(forClass = ConfigToken::class) +class ConfigTokenSerializer : KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ConfigToken") { + element>("config") + } + + override fun serialize(encoder: Encoder, value: ConfigToken) { + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, MapSerializer(String.serializer(), String.serializer()), value.config) + } + } + + override fun deserialize(decoder: Decoder): ConfigToken { + return decoder.decodeStructure(descriptor) { + var config: Map? = null + + loop@ while (true) { + when (val index = decodeElementIndex(descriptor)) { + DECODE_DONE -> break@loop + + 0 -> config = decodeSerializableElement(descriptor, 0, MapSerializer(String.serializer(), String.serializer())) + + else -> throw SerializationException("Unexpected index $index") + } + } + + ConfigToken(config) + } + } +} \ No newline at end of file diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt index 1843acb9..7fce5a18 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt @@ -3,10 +3,15 @@ package org.axonframework.extensions.kotlin.serialization import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.contextual +import org.axonframework.eventhandling.tokenstore.ConfigToken +import org.axonframework.extensions.kotlin.serialization.serializer.ConfigTokenSerializer import org.axonframework.serialization.AnnotationRevisionResolver import org.axonframework.serialization.ChainingConverter import org.axonframework.serialization.SerializedType import org.axonframework.serialization.SimpleSerializedObject +import org.axonframework.serialization.UnknownSerializedType import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -65,31 +70,65 @@ class KotlinSerializerTest { fun deserialize() { val serializer = kotlinSerializer() - val nullDeserialized: Any? = serializer.deserialize(SimpleSerializedObject( - "", - String::class.java, - SerializedType.emptyType() - )) + val nullDeserialized: Any? = serializer.deserialize( + SimpleSerializedObject( + "", + String::class.java, + SerializedType.emptyType() + ) + ) assertNull(nullDeserialized) - val emptyDeserialized: Any? = serializer.deserialize(SimpleSerializedObject( - """{"name":"","value":null}""", - String::class.java, - TestData::class.java.name, - null - )) + val emptyDeserialized: Any? = serializer.deserialize( + SimpleSerializedObject( + """{"name":"","value":null}""", + String::class.java, + TestData::class.java.name, + null + ) + ) assertNotNull(emptyDeserialized as TestData) assertEquals(emptyDeserialized.name, "") assertEquals(emptyDeserialized.value, null) - val filledDeserialized: Any? = serializer.deserialize(SimpleSerializedObject( - """{"name":"name","value":1.23}""", - String::class.java, - TestData::class.java.name, - null - )) + val filledDeserialized: Any? = serializer.deserialize( + SimpleSerializedObject( + """{"name":"name","value":1.23}""", + String::class.java, + TestData::class.java.name, + null + ) + ) assertNotNull(filledDeserialized as TestData) assertEquals(filledDeserialized.name, "name") assertEquals(filledDeserialized.value, 1.23f) + + val unknownDeserializedType: Any? = serializer.deserialize( + SimpleSerializedObject( + """anything""", + String::class.java, + UnknownSerializedType::class.java.name, + null + ) + ) + assertNotNull(unknownDeserializedType as UnknownSerializedType) + } + + @Test + fun `example of custom serializer for ConfigToken`() { + val serializer = kotlinSerializer { + json = Json { + serializersModule = SerializersModule { + contextual(ConfigTokenSerializer()) + } + } + } + + val tokenBefore = ConfigToken(mapOf("test" to "value")) + val serialized = serializer.serialize(tokenBefore, String::class.java) + assertEquals("""{"config":{"test":"value"}}""", serialized.data) + val token: ConfigToken? = serializer.deserialize(serialized) + assertNotNull(token as ConfigToken) + assertEquals("value", token.get("test")) } } \ No newline at end of file From edb6903e90cfe970ebadbdb0a768fbba906138dc Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Thu, 3 Jun 2021 20:53:26 +0200 Subject: [PATCH 07/13] One more default --- .../extensions/kotlin/serialization/KotlinSerializer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index 085798a2..2624110c 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -54,7 +54,7 @@ import org.axonframework.serialization.SerializationException as AxonSerializati class KotlinSerializer( private val revisionResolver: RevisionResolver = AnnotationRevisionResolver(), private val converter: Converter = ChainingConverter(), - private val json: Json + private val json: Json = Json, ) : Serializer { private val serializerCache: MutableMap, KSerializer<*>> = mutableMapOf() From b8fbd02805ac332b88aa302cfa741cd46929bfdd Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Thu, 3 Jun 2021 21:09:11 +0200 Subject: [PATCH 08/13] Byte arrays --- .../kotlin/serialization/KotlinSerializer.kt | 10 ++++++++++ .../kotlin/serialization/KotlinSerializerTest.kt | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index 2624110c..cc98d0a7 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -70,6 +70,13 @@ class KotlinSerializer( typeForClass(type) ) + expectedRepresentation.isAssignableFrom(ByteArray::class.java) -> + SimpleSerializedObject( + (if (value == null) ByteArray(0) else json.encodeToString(type.serializer(), value).toByteArray()) as T, + expectedRepresentation, + typeForClass(type) + ) + expectedRepresentation.isAssignableFrom(JsonElement::class.java) -> SimpleSerializedObject( (if (value == null) JsonNull else json.encodeToJsonElement(type.serializer(), value)) as T, @@ -109,6 +116,9 @@ class KotlinSerializer( serializedObject.contentType.isAssignableFrom(String::class.java) -> json.decodeFromString(serializer, serializedObject.data as String) + serializedObject.contentType.isAssignableFrom(ByteArray::class.java) -> + json.decodeFromString(serializer, (serializedObject.data as ByteArray).decodeToString()) + serializedObject.contentType.isAssignableFrom(JsonElement::class.java) -> json.decodeFromJsonElement(serializer, serializedObject.data as JsonElement) diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt index 7fce5a18..19808710 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt @@ -114,6 +114,13 @@ class KotlinSerializerTest { assertNotNull(unknownDeserializedType as UnknownSerializedType) } + @Test + fun `byte arrays`() { + val serializer = kotlinSerializer() + + assertNotNull(serializer.deserialize(serializer.serialize(TestData("name", null), ByteArray::class.java))) + } + @Test fun `example of custom serializer for ConfigToken`() { val serializer = kotlinSerializer { From c1130408230d387b2121dd2ecbc8ac593588be17 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Thu, 3 Jun 2021 21:41:29 +0200 Subject: [PATCH 09/13] Use chaining converters, add tests for input stream, JSON elements and byte arrays --- .../kotlin/serialization/KotlinSerializer.kt | 60 +++++++------------ .../serialization/KotlinSerializerTest.kt | 17 ++++++ 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index cc98d0a7..338d3402 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -62,39 +62,31 @@ class KotlinSerializer( override fun serialize(value: Any?, expectedRepresentation: Class): SerializedObject { try { val type = ObjectUtils.nullSafeTypeOf(value) - return when { - expectedRepresentation.isAssignableFrom(String::class.java) -> - SimpleSerializedObject( - (if (value == null) "null" else json.encodeToString(type.serializer(), value)) as T, - expectedRepresentation, - typeForClass(type) - ) - - expectedRepresentation.isAssignableFrom(ByteArray::class.java) -> - SimpleSerializedObject( - (if (value == null) ByteArray(0) else json.encodeToString(type.serializer(), value).toByteArray()) as T, - expectedRepresentation, - typeForClass(type) - ) - - expectedRepresentation.isAssignableFrom(JsonElement::class.java) -> - SimpleSerializedObject( - (if (value == null) JsonNull else json.encodeToJsonElement(type.serializer(), value)) as T, - expectedRepresentation, - typeForClass(type) - ) - - else -> - throw AxonSerializationException("Cannot serialize type $type to representation $expectedRepresentation. String and JsonElement are supported.") + + if (expectedRepresentation.isAssignableFrom(JsonElement::class.java)) { + return SimpleSerializedObject( + (if (value == null) JsonNull else json.encodeToJsonElement(type.serializer(), value)) as T, + expectedRepresentation, + typeForClass(type) + ) } + + // By default, encode to String. This can be converted to other types by the converter + val stringSerialized: SerializedObject = SimpleSerializedObject( + (if (value == null) "null" else json.encodeToString(type.serializer(), value)), + String::class.java, + typeForClass(type) + ) + + return converter.convert(stringSerialized, expectedRepresentation) } catch (ex: SerializationException) { throw AxonSerializationException("Cannot serialize type ${value?.javaClass?.name} to representation $expectedRepresentation.", ex) } } override fun canSerializeTo(expectedRepresentation: Class): Boolean = - expectedRepresentation == String::class.java || - expectedRepresentation == JsonElement::class.java + expectedRepresentation == JsonElement::class.java || + converter.canConvert(String::class.java, expectedRepresentation) override fun deserialize(serializedObject: SerializedObject?): T? { try { @@ -112,19 +104,13 @@ class KotlinSerializer( } val serializer: KSerializer = foundType.serializer() as KSerializer - return when { - serializedObject.contentType.isAssignableFrom(String::class.java) -> - json.decodeFromString(serializer, serializedObject.data as String) - serializedObject.contentType.isAssignableFrom(ByteArray::class.java) -> - json.decodeFromString(serializer, (serializedObject.data as ByteArray).decodeToString()) - - serializedObject.contentType.isAssignableFrom(JsonElement::class.java) -> - json.decodeFromJsonElement(serializer, serializedObject.data as JsonElement) - - else -> - throw AxonSerializationException("Cannot deserialize from content type ${serializedObject.contentType} to type ${serializedObject.type}. String and JsonElement are supported.") + if (serializedObject.contentType.isAssignableFrom(JsonElement::class.java)) { + return json.decodeFromJsonElement(serializer, serializedObject.data as JsonElement) } + + val stringSerialized: SerializedObject = converter.convert(serializedObject, String::class.java) + return json.decodeFromString(serializer, stringSerialized.data) } catch (ex: SerializationException) { throw AxonSerializationException( "Could not deserialize from content type ${serializedObject?.contentType} to type ${serializedObject?.type}", diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt index 19808710..7b2430c3 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt @@ -15,6 +15,7 @@ import org.axonframework.serialization.UnknownSerializedType import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test +import java.io.InputStream import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -34,7 +35,9 @@ class KotlinSerializerTest { val serializer = kotlinSerializer() assertTrue(serializer.canSerializeTo(String::class.java)) + assertTrue(serializer.canSerializeTo(ByteArray::class.java)) assertTrue(serializer.canSerializeTo(JsonElement::class.java)) + assertTrue(serializer.canSerializeTo(InputStream::class.java)) } @Test @@ -121,6 +124,20 @@ class KotlinSerializerTest { assertNotNull(serializer.deserialize(serializer.serialize(TestData("name", null), ByteArray::class.java))) } + @Test + fun `JSON elements`() { + val serializer = kotlinSerializer() + + assertNotNull(serializer.deserialize(serializer.serialize(TestData("name", null), JsonElement::class.java))) + } + + @Test + fun `input stream`() { + val serializer = kotlinSerializer() + + assertNotNull(serializer.deserialize(serializer.serialize(TestData("name", null), InputStream::class.java))) + } + @Test fun `example of custom serializer for ConfigToken`() { val serializer = kotlinSerializer { From 7acce4cc01da52414950d353a80a1917573ed1a0 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Mon, 20 Sep 2021 20:50:55 +0200 Subject: [PATCH 10/13] Remove the configuration DSL class --- .../kotlin/serialization/KotlinSerializer.kt | 37 ------------------- .../serialization/KotlinSerializerTest.kt | 27 +++++++------- 2 files changed, 14 insertions(+), 50 deletions(-) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index 338d3402..87f63914 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -172,41 +172,4 @@ class KotlinSerializer( override fun getConverter(): Converter = converter -} - -/** - * Configuration which will be used to construct a KotlinSerializer. - * This class is used in the kotlinSerializer DSL function. - * - * @see KotlinSerializer - * @see kotlinSerializer - */ -class KotlinSerializerConfiguration { - var revisionResolver: RevisionResolver = AnnotationRevisionResolver() - var converter: Converter = ChainingConverter() - var json: Json = Json -} - -/** - * DSL function to configure a new KotlinSerializer. - * - * Usage example: - * - * val serializer: KotlinSerializer = kotlinSerializer { - * json = Json - * converter = ChainingConverter() - * revisionResolver = AnnotationRevisionResolver() - * } - * - * - * @see KotlinSerializer - */ -fun kotlinSerializer(init: KotlinSerializerConfiguration.() -> Unit = {}): KotlinSerializer { - val configuration = KotlinSerializerConfiguration() - configuration.init() - return KotlinSerializer( - configuration.revisionResolver, - configuration.converter, - configuration.json, - ) } \ No newline at end of file diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt index 7b2430c3..15bc7448 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializerTest.kt @@ -32,7 +32,7 @@ class KotlinSerializerTest { @Test fun canSerializeTo() { - val serializer = kotlinSerializer() + val serializer = KotlinSerializer() assertTrue(serializer.canSerializeTo(String::class.java)) assertTrue(serializer.canSerializeTo(ByteArray::class.java)) @@ -42,17 +42,18 @@ class KotlinSerializerTest { @Test fun `configuration options`() { - val serializer = kotlinSerializer { - json = Json - converter = ChainingConverter() - revisionResolver = AnnotationRevisionResolver() - } + val serializer = KotlinSerializer( + revisionResolver = AnnotationRevisionResolver(), + converter = ChainingConverter(), + json = Json, + ) + assertNotNull(serializer) } @Test fun serialize() { - val serializer = kotlinSerializer() + val serializer = KotlinSerializer() val emptySerialized = serializer.serialize(TestData("", null), String::class.java) assertEquals("SimpleSerializedType[org.axonframework.extensions.kotlin.serialization.KotlinSerializerTest\$TestData] (revision null)", emptySerialized.type.toString()) @@ -71,7 +72,7 @@ class KotlinSerializerTest { @Test fun deserialize() { - val serializer = kotlinSerializer() + val serializer = KotlinSerializer() val nullDeserialized: Any? = serializer.deserialize( SimpleSerializedObject( @@ -119,34 +120,34 @@ class KotlinSerializerTest { @Test fun `byte arrays`() { - val serializer = kotlinSerializer() + val serializer = KotlinSerializer() assertNotNull(serializer.deserialize(serializer.serialize(TestData("name", null), ByteArray::class.java))) } @Test fun `JSON elements`() { - val serializer = kotlinSerializer() + val serializer = KotlinSerializer() assertNotNull(serializer.deserialize(serializer.serialize(TestData("name", null), JsonElement::class.java))) } @Test fun `input stream`() { - val serializer = kotlinSerializer() + val serializer = KotlinSerializer() assertNotNull(serializer.deserialize(serializer.serialize(TestData("name", null), InputStream::class.java))) } @Test fun `example of custom serializer for ConfigToken`() { - val serializer = kotlinSerializer { + val serializer = KotlinSerializer( json = Json { serializersModule = SerializersModule { contextual(ConfigTokenSerializer()) } } - } + ) val tokenBefore = ConfigToken(mapOf("test" to "value")) val serialized = serializer.serialize(tokenBefore, String::class.java) From e0f411ac1c60db714809cc0075c502e67d144bac Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 16 Apr 2022 13:25:23 +0200 Subject: [PATCH 11/13] Kotlin serialization 1.3.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index febf81ad..ea7e6ece 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ 1.6.20 2.13.2 2.1.21 - 1.2.1 + 1.3.2 1.12.3 5.8.2 1.7.36 From 8d7b70d7c1407917b5d7e185cfb7d2f8dcc255b4 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 16 Apr 2022 13:38:06 +0200 Subject: [PATCH 12/13] better check for JsonElement assignability --- .../extensions/kotlin/serialization/KotlinSerializer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index 87f63914..2c3eb4fc 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -85,7 +85,7 @@ class KotlinSerializer( } override fun canSerializeTo(expectedRepresentation: Class): Boolean = - expectedRepresentation == JsonElement::class.java || + expectedRepresentation.isAssignableFrom(JsonElement::class.java) || converter.canConvert(String::class.java, expectedRepresentation) override fun deserialize(serializedObject: SerializedObject?): T? { From 34844c8f27034764c14e2c0ac962837e3fb1dd05 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 16 Apr 2022 14:39:51 +0200 Subject: [PATCH 13/13] Use surrogate in example. `@Serializer(forClass = ...)` still doesnt work --- .../serializer/ConfigTokenSerializer.kt | 61 ++++++++----------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/serializer/ConfigTokenSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/serializer/ConfigTokenSerializer.kt index 2aa28289..f923d420 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/serializer/ConfigTokenSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/serializer/ConfigTokenSerializer.kt @@ -2,48 +2,37 @@ package org.axonframework.extensions.kotlin.serialization.serializer import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationException +import kotlinx.serialization.Serializable import kotlinx.serialization.Serializer -import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.buildClassSerialDescriptor -import kotlinx.serialization.descriptors.element -import kotlinx.serialization.encoding.CompositeDecoder.Companion.DECODE_DONE import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.encoding.decodeStructure -import kotlinx.serialization.encoding.encodeStructure import org.axonframework.eventhandling.tokenstore.ConfigToken +@Serializable +data class ConfigTokenSurrogate( + private val config: Map +) { + fun toToken() = ConfigToken(config) +} + +fun ConfigToken.toSurrogate() = ConfigTokenSurrogate(config) + @OptIn(ExperimentalSerializationApi::class) @Serializer(forClass = ConfigToken::class) class ConfigTokenSerializer : KSerializer { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ConfigToken") { - element>("config") - } - - override fun serialize(encoder: Encoder, value: ConfigToken) { - encoder.encodeStructure(descriptor) { - encodeSerializableElement(descriptor, 0, MapSerializer(String.serializer(), String.serializer()), value.config) - } - } - - override fun deserialize(decoder: Decoder): ConfigToken { - return decoder.decodeStructure(descriptor) { - var config: Map? = null - - loop@ while (true) { - when (val index = decodeElementIndex(descriptor)) { - DECODE_DONE -> break@loop - - 0 -> config = decodeSerializableElement(descriptor, 0, MapSerializer(String.serializer(), String.serializer())) - - else -> throw SerializationException("Unexpected index $index") - } - } - - ConfigToken(config) - } - } -} \ No newline at end of file + override val descriptor: SerialDescriptor = ConfigTokenSurrogate.serializer().descriptor + + override fun serialize(encoder: Encoder, value: ConfigToken) = + encoder.encodeSerializableValue(ConfigTokenSurrogate.serializer(), value.toSurrogate()) + + override fun deserialize(decoder: Decoder): ConfigToken = + decoder.decodeSerializableValue(ConfigTokenSurrogate.serializer()).toToken() +} + +// The following does not work, emits compiler errors. +// Also with the `-Xuse-ir` Kotlin compiler argument (version 1.6.20) + +//@OptIn(ExperimentalSerializationApi::class) +//@Serializer(forClass = ConfigToken::class) +//object ConfigTokenSerializer \ No newline at end of file