1
+ package org.axonframework.extensions.kotlin.serialization
2
+
3
+ import kotlinx.serialization.KSerializer
4
+ import kotlinx.serialization.SerializationException
5
+ import kotlinx.serialization.builtins.MapSerializer
6
+ import kotlinx.serialization.builtins.serializer
7
+ import kotlinx.serialization.descriptors.SerialDescriptor
8
+ import kotlinx.serialization.encoding.Decoder
9
+ import kotlinx.serialization.encoding.Encoder
10
+ import kotlinx.serialization.json.*
11
+ import org.axonframework.messaging.MetaData
12
+ import java.time.Instant
13
+ import java.util.UUID
14
+
15
+ /* *
16
+ * A Kotlinx [KSerializer] for Axon Framework's [MetaData] type, supporting serialization across any format.
17
+ *
18
+ * This serializer converts a [MetaData] instance to a JSON-encoded [String] using a recursive conversion
19
+ * of all entries into [JsonElement]s. This JSON string is then serialized using [String.serializer],
20
+ * ensuring compatibility with any [kotlinx.serialization.encoding.Encoder]—including formats such as JSON, CBOR, ProtoBuf, or Avro.
21
+ *
22
+ * ### Supported value types
23
+ * Each entry in the MetaData map must conform to one of the following:
24
+ * - Primitives: [String], [Int], [Long], [Float], [Double], [Boolean]
25
+ * - Complex types: [UUID], [Instant]
26
+ * - Collections: [Collection], [List], [Set]
27
+ * - Arrays: [Array]
28
+ * - Nested Maps: [Map] with keys convertible to [String]
29
+ *
30
+ * ### Limitations
31
+ * - Custom types that do not fall into the above categories will throw a [SerializationException]
32
+ * - Deserialized non-primitive types (like [UUID], [Instant]) are restored as [String], not their original types
33
+ *
34
+ * This serializer guarantees structural integrity of nested metadata (e.g. map within list within map), while remaining format-agnostic.
35
+ *
36
+ * @author Mateusz Nowak
37
+ * @since 4.11.1
38
+ */
39
+ object MetaDataSerializer : KSerializer<MetaData> {
40
+
41
+ private val json = Json { encodeDefaults = true ; ignoreUnknownKeys = true }
42
+
43
+ override val descriptor: SerialDescriptor = String .serializer().descriptor
44
+
45
+ override fun serialize (encoder : Encoder , value : MetaData ) {
46
+ val map: Map <String , JsonElement > = value.entries.associate { (key, rawValue) ->
47
+ key to toJsonElement(rawValue)
48
+ }
49
+ val jsonString = json.encodeToString(MapSerializer (String .serializer(), JsonElement .serializer()), map)
50
+ encoder.encodeSerializableValue(String .serializer(), jsonString)
51
+ }
52
+
53
+ override fun deserialize (decoder : Decoder ): MetaData {
54
+ val jsonString = decoder.decodeSerializableValue(String .serializer())
55
+ val map = json.decodeFromString(MapSerializer (String .serializer(), JsonElement .serializer()), jsonString)
56
+ val reconstructed = map.mapValues { (_, jsonElement) -> fromJsonElement(jsonElement) }
57
+ return MetaData (reconstructed)
58
+ }
59
+
60
+ private fun toJsonElement (value : Any? ): JsonElement = when (value) {
61
+ null -> JsonNull
62
+ is String -> JsonPrimitive (value)
63
+ is Boolean -> JsonPrimitive (value)
64
+ is Int -> JsonPrimitive (value)
65
+ is Long -> JsonPrimitive (value)
66
+ is Float -> JsonPrimitive (value)
67
+ is Double -> JsonPrimitive (value)
68
+ is UUID -> JsonPrimitive (value.toString())
69
+ is Instant -> JsonPrimitive (value.toString())
70
+ is Map <* , * > -> JsonObject (value.entries.associate { (k, v) -> k.toString() to toJsonElement(v) })
71
+ is Collection <* > -> JsonArray (value.map { toJsonElement(it) })
72
+ is Array <* > -> JsonArray (value.map { toJsonElement(it) })
73
+ else -> throw SerializationException (" Unsupported type: ${value::class } " )
74
+ }
75
+
76
+ private fun fromJsonElement (element : JsonElement ): Any? = when (element) {
77
+ is JsonNull -> null
78
+ is JsonPrimitive -> {
79
+ if (element.isString) {
80
+ element.content
81
+ } else {
82
+ element.booleanOrNull
83
+ ? : element.intOrNull
84
+ ? : element.longOrNull
85
+ ? : element.floatOrNull
86
+ ? : element.doubleOrNull
87
+ ? : element.content
88
+ }
89
+ }
90
+ is JsonObject -> element.mapValues { fromJsonElement(it.value) }
91
+ is JsonArray -> element.map { fromJsonElement(it) }
92
+ }
93
+ }
0 commit comments