@@ -5,6 +5,7 @@ import kotlinx.serialization.SerializationException
5
5
import kotlinx.serialization.builtins.MapSerializer
6
6
import kotlinx.serialization.builtins.serializer
7
7
import kotlinx.serialization.descriptors.SerialDescriptor
8
+ import kotlinx.serialization.encodeToString
8
9
import kotlinx.serialization.encoding.Decoder
9
10
import kotlinx.serialization.encoding.Encoder
10
11
import kotlinx.serialization.json.*
@@ -13,11 +14,44 @@ import java.time.Instant
13
14
import java.util.UUID
14
15
15
16
/* *
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.
17
51
*
18
52
* 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 .
21
55
*
22
56
* ### Supported value types
23
57
* Each entry in the MetaData map must conform to one of the following:
@@ -31,13 +65,12 @@ import java.util.UUID
31
65
* - Custom types that do not fall into the above categories will throw a [SerializationException]
32
66
* - Deserialized non-primitive types (like [UUID], [Instant]) are restored as [String], not their original types
33
67
*
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.
35
69
*
36
70
* @author Mateusz Nowak
37
71
* @since 4.11.1
38
72
*/
39
- object MetaDataSerializer : KSerializer<MetaData> {
40
-
73
+ object StringMetaDataSerializer : KSerializer<MetaData> {
41
74
private val json = Json { encodeDefaults = true ; ignoreUnknownKeys = true }
42
75
43
76
override val descriptor: SerialDescriptor = String .serializer().descriptor
@@ -46,14 +79,93 @@ object MetaDataSerializer : KSerializer<MetaData> {
46
79
val map: Map <String , JsonElement > = value.entries.associate { (key, rawValue) ->
47
80
key to toJsonElement(rawValue)
48
81
}
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)
51
162
}
52
163
53
164
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
+ }
57
169
return MetaData (reconstructed)
58
170
}
59
171
@@ -67,7 +179,9 @@ object MetaDataSerializer : KSerializer<MetaData> {
67
179
is Double -> JsonPrimitive (value)
68
180
is UUID -> JsonPrimitive (value.toString())
69
181
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
+ })
71
185
is Collection <* > -> JsonArray (value.map { toJsonElement(it) })
72
186
is Array <* > -> JsonArray (value.map { toJsonElement(it) })
73
187
else -> throw SerializationException (" Unsupported type: ${value::class } " )
@@ -79,12 +193,8 @@ object MetaDataSerializer : KSerializer<MetaData> {
79
193
if (element.isString) {
80
194
element.content
81
195
} 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
88
198
}
89
199
}
90
200
is JsonObject -> element.mapValues { fromJsonElement(it.value) }
0 commit comments