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