@@ -5,6 +5,7 @@ import kotlinx.serialization.SerializationException
55import kotlinx.serialization.builtins.MapSerializer
66import kotlinx.serialization.builtins.serializer
77import kotlinx.serialization.descriptors.SerialDescriptor
8+ import kotlinx.serialization.encodeToString
89import kotlinx.serialization.encoding.Decoder
910import kotlinx.serialization.encoding.Encoder
1011import kotlinx.serialization.json.*
@@ -13,11 +14,44 @@ import java.time.Instant
1314import java.util.UUID
1415
1516/* *
16- * A Kotlinx [KSerializer] for Axon Framework's [MetaData] type, supporting serialization across any format.
17+ * A composite Kotlinx [KSerializer] for Axon Framework's [MetaData] type that selects the
18+ * appropriate serializer based on the encoder/decoder type.
19+ *
20+ * This serializer delegates to:
21+ * - [JsonMetaDataSerializer] when used with [JsonEncoder]/[JsonDecoder]
22+ * - [StringMetaDataSerializer] for all other encoder/decoder types
23+ *
24+ * This allows efficient JSON serialization without unnecessary string encoding, while
25+ * maintaining compatibility with all other serialization formats through string-based
26+ * serialization.
27+ *
28+ * @author Mateusz Nowak
29+ * @since 4.11.2
30+ */
31+ object ComposedMetaDataSerializer : KSerializer<MetaData> {
32+ override val descriptor: SerialDescriptor = StringMetaDataSerializer .descriptor
33+
34+ override fun serialize (encoder : Encoder , value : MetaData ) {
35+ when (encoder) {
36+ is JsonEncoder -> JsonMetaDataSerializer .serialize(encoder, value)
37+ else -> StringMetaDataSerializer .serialize(encoder, value)
38+ }
39+ }
40+
41+ override fun deserialize (decoder : Decoder ): MetaData {
42+ return when (decoder) {
43+ is JsonDecoder -> JsonMetaDataSerializer .deserialize(decoder)
44+ else -> StringMetaDataSerializer .deserialize(decoder)
45+ }
46+ }
47+ }
48+
49+ /* *
50+ * A Kotlinx [KSerializer] for Axon Framework's [MetaData] type, suitable for serialization across any format.
1751 *
1852 * 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 .
53+ * of all entries into [JsonElement]s. This JSON string is then serialized using [String.serializer() ],
54+ * ensuring compatibility with any serialization format .
2155 *
2256 * ### Supported value types
2357 * Each entry in the MetaData map must conform to one of the following:
@@ -31,13 +65,12 @@ import java.util.UUID
3165 * - Custom types that do not fall into the above categories will throw a [SerializationException]
3266 * - Deserialized non-primitive types (like [UUID], [Instant]) are restored as [String], not their original types
3367 *
34- * This serializer guarantees structural integrity of nested metadata (e.g. map within list within map), while remaining format-agnostic.
68+ * This serializer guarantees structural integrity of nested metadata while remaining format-agnostic.
3569 *
3670 * @author Mateusz Nowak
3771 * @since 4.11.1
3872 */
39- object MetaDataSerializer : KSerializer<MetaData> {
40-
73+ object StringMetaDataSerializer : KSerializer<MetaData> {
4174 private val json = Json { encodeDefaults = true ; ignoreUnknownKeys = true }
4275
4376 override val descriptor: SerialDescriptor = String .serializer().descriptor
@@ -46,14 +79,93 @@ object MetaDataSerializer : KSerializer<MetaData> {
4679 val map: Map <String , JsonElement > = value.entries.associate { (key, rawValue) ->
4780 key to toJsonElement(rawValue)
4881 }
49- val jsonString = json.encodeToString(MapSerializer (String .serializer(), JsonElement .serializer()), map)
50- encoder.encodeSerializableValue(String .serializer(), jsonString)
82+ val jsonString = json.encodeToString(JsonObject (map))
83+ encoder.encodeString(jsonString)
84+ }
85+
86+ override fun deserialize (decoder : Decoder ): MetaData {
87+ val jsonString = decoder.decodeString()
88+ val jsonObject = json.parseToJsonElement(jsonString).jsonObject
89+ val reconstructed = jsonObject.mapValues { (_, jsonElement) ->
90+ fromJsonElement(jsonElement)
91+ }
92+ return MetaData (reconstructed)
93+ }
94+
95+ private fun toJsonElement (value : Any? ): JsonElement = when (value) {
96+ null -> JsonNull
97+ is String -> JsonPrimitive (value)
98+ is Boolean -> JsonPrimitive (value)
99+ is Int -> JsonPrimitive (value)
100+ is Long -> JsonPrimitive (value)
101+ is Float -> JsonPrimitive (value)
102+ is Double -> JsonPrimitive (value)
103+ is UUID -> JsonPrimitive (value.toString())
104+ is Instant -> JsonPrimitive (value.toString())
105+ is Map <* , * > -> JsonObject (value.entries.associate { (k, v) ->
106+ k.toString() to toJsonElement(v)
107+ })
108+ is Collection <* > -> JsonArray (value.map { toJsonElement(it) })
109+ is Array <* > -> JsonArray (value.map { toJsonElement(it) })
110+ else -> throw SerializationException (" Unsupported type: ${value::class } " )
111+ }
112+
113+ private fun fromJsonElement (element : JsonElement ): Any? = when (element) {
114+ is JsonNull -> null
115+ is JsonPrimitive -> {
116+ if (element.isString) {
117+ element.content
118+ } else {
119+ element.booleanOrNull ? : element.intOrNull ? : element.longOrNull ? :
120+ element.floatOrNull ? : element.doubleOrNull ? : element.content
121+ }
122+ }
123+ is JsonObject -> element.mapValues { fromJsonElement(it.value) }
124+ is JsonArray -> element.map { fromJsonElement(it) }
125+ }
126+ }
127+
128+ /* *
129+ * A Kotlinx [KSerializer] for Axon Framework's [MetaData] type, optimized for JSON serialization.
130+ *
131+ * This serializer converts a [MetaData] instance directly to a JSON object structure,
132+ * avoiding the string-encoding that [StringMetaDataSerializer] uses. This ensures JSON values
133+ * are properly encoded without quote escaping.
134+ *
135+ * ### Supported value types
136+ * Each entry in the MetaData map must conform to one of the following:
137+ * - Primitives: [String], [Int], [Long], [Float], [Double], [Boolean]
138+ * - Complex types: [UUID], [Instant]
139+ * - Collections: [Collection], [List], [Set]
140+ * - Arrays: [Array]
141+ * - Nested Maps: [Map] with keys convertible to [String]
142+ *
143+ * ### Limitations
144+ * - Custom types that do not fall into the above categories will throw a [SerializationException]
145+ * - Deserialized non-primitive types (like [UUID], [Instant]) are restored as [String], not their original types
146+ *
147+ * This serializer is specifically optimized for JSON serialization formats.
148+ *
149+ * @author Mateusz Nowak
150+ * @since 4.11.2
151+ */
152+ object JsonMetaDataSerializer : KSerializer<MetaData> {
153+ private val mapSerializer = MapSerializer (String .serializer(), JsonElement .serializer())
154+
155+ override val descriptor: SerialDescriptor = mapSerializer.descriptor
156+
157+ override fun serialize (encoder : Encoder , value : MetaData ) {
158+ val jsonMap = value.entries.associate { (key, rawValue) ->
159+ key to toJsonElement(rawValue)
160+ }
161+ encoder.encodeSerializableValue(mapSerializer, jsonMap)
51162 }
52163
53164 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) }
165+ val jsonMap = decoder.decodeSerializableValue(mapSerializer)
166+ val reconstructed = jsonMap.mapValues { (_, jsonElement) ->
167+ fromJsonElement(jsonElement)
168+ }
57169 return MetaData (reconstructed)
58170 }
59171
@@ -67,7 +179,9 @@ object MetaDataSerializer : KSerializer<MetaData> {
67179 is Double -> JsonPrimitive (value)
68180 is UUID -> JsonPrimitive (value.toString())
69181 is Instant -> JsonPrimitive (value.toString())
70- is Map <* , * > -> JsonObject (value.entries.associate { (k, v) -> k.toString() to toJsonElement(v) })
182+ is Map <* , * > -> JsonObject (value.entries.associate { (k, v) ->
183+ k.toString() to toJsonElement(v)
184+ })
71185 is Collection <* > -> JsonArray (value.map { toJsonElement(it) })
72186 is Array <* > -> JsonArray (value.map { toJsonElement(it) })
73187 else -> throw SerializationException (" Unsupported type: ${value::class } " )
@@ -79,12 +193,8 @@ object MetaDataSerializer : KSerializer<MetaData> {
79193 if (element.isString) {
80194 element.content
81195 } else {
82- element.booleanOrNull
83- ? : element.intOrNull
84- ? : element.longOrNull
85- ? : element.floatOrNull
86- ? : element.doubleOrNull
87- ? : element.content
196+ element.booleanOrNull ? : element.intOrNull ? : element.longOrNull ? :
197+ element.floatOrNull ? : element.doubleOrNull ? : element.content
88198 }
89199 }
90200 is JsonObject -> element.mapValues { fromJsonElement(it.value) }
0 commit comments