11package org.axonframework.extensions.kotlin.serialization
22
3+ import kotlinx.serialization.ExperimentalSerializationApi
4+ import kotlinx.serialization.InternalSerializationApi
35import kotlinx.serialization.KSerializer
4- import kotlinx.serialization.PolymorphicSerializer
56import kotlinx.serialization.builtins.MapSerializer
6- import kotlinx.serialization.builtins.nullable
77import kotlinx.serialization.builtins.serializer
88import kotlinx.serialization.descriptors.SerialDescriptor
9- import kotlinx.serialization.descriptors.buildClassSerialDescriptor
10- import kotlinx.serialization.encoding.CompositeDecoder
119import kotlinx.serialization.encoding.Decoder
1210import kotlinx.serialization.encoding.Encoder
13- import kotlinx.serialization.encoding.decodeStructure
14- import kotlinx.serialization.encoding.encodeStructure
11+ import kotlinx.serialization.json.*
12+ import kotlinx.serialization.serializer
1513import org.axonframework.messaging.MetaData
1614
1715/* *
18- * Serializer for Axon Framework's [MetaData] class.
19- * Since MetaData implements Map<String, Object>, we can use a map serializer
20- * but need special handling for the polymorphic values.
16+ * A serializer for Axon's MetaData class that works with any encoder format.
17+ * MetaData is essentially a map that can contain primitive values and custom objects.
18+ *
19+ * This serializer uses a two-step approach:
20+ * 1. First, we convert the MetaData to a serializable representation using JSON as an intermediate format
21+ * 2. Then we use the target encoder to write the final output
22+ *
23+ * This approach allows us to properly handle any type of value in the MetaData, including custom objects.
24+ *
25+ * @since 4.11.1
2126 */
2227object MetaDataSerializer : KSerializer<MetaData> {
23- // We use a map serializer with String keys and polymorphic Any values
24- private val mapSerializer = MapSerializer (
25- keySerializer = String .serializer(),
26- valueSerializer = PolymorphicSerializer (Any ::class ).nullable
27- )
28-
29- override val descriptor: SerialDescriptor = buildClassSerialDescriptor(" org.axonframework.messaging.MetaData" ) {
30- element(" entries" , mapSerializer.descriptor)
31- }
28+ // We use Json as an intermediary to convert arbitrary objects to a serializable form
29+ private val json = Json { encodeDefaults = true ; ignoreUnknownKeys = true }
30+
31+ // Map with String keys and JsonElement values (which can represent any value)
32+ private val mapSerializer = MapSerializer (String .serializer(), JsonElement .serializer())
33+
34+ override val descriptor: SerialDescriptor = mapSerializer.descriptor
3235
3336 override fun serialize (encoder : Encoder , value : MetaData ) {
34- encoder.encodeStructure(descriptor) {
35- encodeSerializableElement(descriptor, 0 , mapSerializer, value)
37+ // Convert each value to JsonElement, which can represent any serializable value
38+ val jsonMap = value.mapValues { (_, value) ->
39+ convertToJsonElement(value)
3640 }
41+
42+ // Serialize the map of JsonElements
43+ mapSerializer.serialize(encoder, jsonMap)
3744 }
3845
3946 override fun deserialize (decoder : Decoder ): MetaData {
40- // Deserialize as a Map, then convert to MetaData
41- return decoder.decodeStructure(descriptor) {
42- var map: Map <String , Any ?>? = null
43- while (true ) {
44- val index = decodeElementIndex(descriptor)
45- if (index == CompositeDecoder .DECODE_DONE ) break
46- when (index) {
47- 0 -> map = decodeSerializableElement(descriptor, index, mapSerializer)
47+ // First deserialize to a Map<String, JsonElement>
48+ val jsonMap = mapSerializer.deserialize(decoder)
49+
50+ // Then convert each JsonElement back to its original type
51+ val resultMap = jsonMap.mapValues { (_, jsonElement) ->
52+ convertFromJsonElement(jsonElement)
53+ }
54+
55+ return MetaData .from(resultMap)
56+ }
57+
58+ /* *
59+ * Convert any value to a JsonElement
60+ */
61+ private fun convertToJsonElement (value : Any? ): JsonElement {
62+ return when (value) {
63+ null -> JsonNull
64+ is JsonElement -> value
65+ is Number -> JsonPrimitive (value)
66+ is Boolean -> JsonPrimitive (value)
67+ is String -> JsonPrimitive (value)
68+ is Enum <* > -> JsonPrimitive (value.name)
69+ // For custom objects, use Json to encode them to a JsonElement
70+ else -> try {
71+ json.encodeToJsonElement(value)
72+ } catch (e: Exception ) {
73+ // If serialization fails, fall back to string representation
74+ JsonPrimitive (value.toString())
75+ }
76+ }
77+ }
78+
79+ /* *
80+ * Convert a JsonElement back to its original type
81+ */
82+ private fun convertFromJsonElement (element : JsonElement ): Any? {
83+ return when (element) {
84+ is JsonNull -> null
85+ is JsonPrimitive -> when {
86+ element.isString -> element.content
87+ element.booleanOrNull != null -> element.boolean
88+ element.longOrNull != null -> element.long
89+ element.doubleOrNull != null -> element.double
90+ else -> element.content
91+ }
92+ is JsonArray -> element.map { convertFromJsonElement(it) }
93+ is JsonObject -> element.mapValues { (_, value) -> convertFromJsonElement(value) }
94+ }
95+ }
96+
97+ /* *
98+ * Extension method to simplify JSON encoding of arbitrary values
99+ */
100+ @OptIn(InternalSerializationApi ::class , ExperimentalSerializationApi ::class )
101+ private fun Json.encodeToJsonElement (value : Any ): JsonElement {
102+ return when (value) {
103+ is JsonElement -> value
104+ else -> {
105+ try {
106+ // Try to find a serializer for this type
107+ val serializer = serializersModule.getContextual(value::class )
108+ ? : value::class .serializer()
109+
110+ @Suppress(" UNCHECKED_CAST" )
111+ encodeToJsonElement(serializer as KSerializer <Any >, value)
112+ } catch (e: Exception ) {
113+ // If we can't serialize it properly, convert to string
114+ JsonPrimitive (value.toString())
48115 }
49116 }
50- MetaData .from(map ? : HashMap <String , Any ?>())
51117 }
52118 }
53119}
0 commit comments