0
# Advanced Serialization
1
2
Advanced serialization interfaces, custom serializers, and low-level JSON processing APIs for specialized use cases requiring fine-grained control over serialization behavior.
3
4
## Capabilities
5
6
### JsonEncoder Interface
7
8
Low-level encoder interface for custom JSON serialization with direct JsonElement access.
9
10
```kotlin { .api }
11
/**
12
* Encoder interface for JSON serialization with JsonElement support
13
* Provides access to the Json instance and direct JsonElement encoding
14
*/
15
@SubclassOptInRequired(SealedSerializationApi::class)
16
interface JsonEncoder : Encoder, CompositeEncoder {
17
/**
18
* The Json instance used for encoding
19
*/
20
val json: Json
21
22
/**
23
* Encode a JsonElement directly to the output
24
* @param element JsonElement to encode
25
*/
26
fun encodeJsonElement(element: JsonElement)
27
}
28
```
29
30
### JsonDecoder Interface
31
32
Low-level decoder interface for custom JSON deserialization with direct JsonElement access.
33
34
```kotlin { .api }
35
/**
36
* Decoder interface for JSON deserialization with JsonElement support
37
* Provides access to the Json instance and direct JsonElement decoding
38
*/
39
@SubclassOptInRequired(SealedSerializationApi::class)
40
interface JsonDecoder : Decoder, CompositeDecoder {
41
/**
42
* The Json instance used for decoding
43
*/
44
val json: Json
45
46
/**
47
* Decode the current JSON input as a JsonElement
48
* @return JsonElement representing the current JSON value
49
*/
50
fun decodeJsonElement(): JsonElement
51
}
52
```
53
54
**Usage Examples:**
55
56
```kotlin
57
import kotlinx.serialization.*
58
import kotlinx.serialization.json.*
59
import kotlinx.serialization.encoding.*
60
import kotlinx.serialization.descriptors.*
61
62
// Custom serializer using JsonEncoder/JsonDecoder
63
object TimestampSerializer : KSerializer<Long> {
64
override val descriptor = PrimitiveSerialDescriptor("Timestamp", PrimitiveKind.LONG)
65
66
override fun serialize(encoder: Encoder, value: Long) {
67
if (encoder is JsonEncoder) {
68
// Custom JSON encoding - store as ISO string
69
val isoString = java.time.Instant.ofEpochMilli(value).toString()
70
encoder.encodeJsonElement(JsonPrimitive(isoString))
71
} else {
72
encoder.encodeLong(value)
73
}
74
}
75
76
override fun deserialize(decoder: Decoder): Long {
77
return if (decoder is JsonDecoder) {
78
// Custom JSON decoding - parse ISO string
79
val element = decoder.decodeJsonElement()
80
if (element is JsonPrimitive && element.isString) {
81
java.time.Instant.parse(element.content).toEpochMilli()
82
} else {
83
element.jsonPrimitive.long
84
}
85
} else {
86
decoder.decodeLong()
87
}
88
}
89
}
90
91
@Serializable
92
data class LogEntry(
93
val message: String,
94
@Serializable(TimestampSerializer::class)
95
val timestamp: Long
96
)
97
98
val entry = LogEntry("Application started", System.currentTimeMillis())
99
val json = Json.encodeToString(entry)
100
val decoded = Json.decodeFromString<LogEntry>(json)
101
```
102
103
### JsonContentPolymorphicSerializer
104
105
Abstract base class for content-based polymorphic serialization where type selection is based on JSON content rather than discriminator fields.
106
107
```kotlin { .api }
108
/**
109
* Abstract serializer for polymorphic serialization based on JSON content
110
* Type selection is performed by examining the JsonElement structure
111
*/
112
abstract class JsonContentPolymorphicSerializer<T : Any>(
113
baseClass: KClass<T>
114
) : KSerializer<T> {
115
/**
116
* Select the appropriate deserializer based on JSON content
117
* @param element JsonElement representing the JSON to deserialize
118
* @return DeserializationStrategy for the appropriate concrete type
119
*/
120
abstract fun selectDeserializer(element: JsonElement): DeserializationStrategy<T>
121
}
122
```
123
124
**Usage Examples:**
125
126
```kotlin
127
import kotlinx.serialization.*
128
import kotlinx.serialization.json.*
129
130
@Serializable
131
sealed class ApiMessage
132
133
@Serializable
134
data class TextMessage(val text: String) : ApiMessage()
135
136
@Serializable
137
data class ImageMessage(val url: String, val width: Int, val height: Int) : ApiMessage()
138
139
@Serializable
140
data class LocationMessage(val latitude: Double, val longitude: Double, val address: String?) : ApiMessage()
141
142
// Content-based polymorphic serializer
143
object ApiMessageSerializer : JsonContentPolymorphicSerializer<ApiMessage>(ApiMessage::class) {
144
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ApiMessage> {
145
val jsonObject = element.jsonObject
146
147
return when {
148
"text" in jsonObject -> TextMessage.serializer()
149
"url" in jsonObject && "width" in jsonObject -> ImageMessage.serializer()
150
"latitude" in jsonObject && "longitude" in jsonObject -> LocationMessage.serializer()
151
else -> throw SerializationException("Unknown message type")
152
}
153
}
154
}
155
156
// Register the serializer
157
val json = Json {
158
serializersModule = SerializersModule {
159
polymorphic(ApiMessage::class) {
160
subclass(TextMessage::class)
161
subclass(ImageMessage::class)
162
subclass(LocationMessage::class)
163
}
164
}
165
}
166
167
// Usage - no discriminator field needed
168
val messages = listOf<ApiMessage>(
169
TextMessage("Hello world"),
170
ImageMessage("https://example.com/image.jpg", 800, 600),
171
LocationMessage(40.7128, -74.0060, "New York, NY")
172
)
173
174
val encoded = json.encodeToString(messages)
175
// Result: [
176
// {"text":"Hello world"},
177
// {"url":"https://example.com/image.jpg","width":800,"height":600},
178
// {"latitude":40.7128,"longitude":-74.0060,"address":"New York, NY"}
179
// ]
180
181
val decoded = json.decodeFromString<List<ApiMessage>>(encoded)
182
```
183
184
### JsonTransformingSerializer
185
186
Abstract base class for transforming JsonElement during serialization and deserialization, allowing modification of JSON structure without changing the data class.
187
188
```kotlin { .api }
189
/**
190
* Abstract serializer for transforming JsonElement during serialization/deserialization
191
* Allows modification of JSON structure without changing the underlying data class
192
*/
193
abstract class JsonTransformingSerializer<T>(
194
private val tSerializer: KSerializer<T>
195
) : KSerializer<T> {
196
override val descriptor: SerialDescriptor = tSerializer.descriptor
197
198
/**
199
* Transform JsonElement after deserialization before converting to object
200
* @param element JsonElement to transform
201
* @return Transformed JsonElement
202
*/
203
open fun transformDeserialize(element: JsonElement): JsonElement = element
204
205
/**
206
* Transform JsonElement after serialization from object
207
* @param element JsonElement to transform
208
* @return Transformed JsonElement
209
*/
210
open fun transformSerialize(element: JsonElement): JsonElement = element
211
}
212
```
213
214
**Usage Examples:**
215
216
```kotlin
217
import kotlinx.serialization.*
218
import kotlinx.serialization.json.*
219
220
@Serializable
221
data class UserData(
222
val id: Int,
223
val name: String,
224
val email: String
225
)
226
227
// Transformer that adds/removes metadata fields
228
object UserDataTransformer : JsonTransformingSerializer<UserData>(UserData.serializer()) {
229
230
override fun transformSerialize(element: JsonElement): JsonElement {
231
val jsonObject = element.jsonObject
232
return buildJsonObject {
233
// Copy original fields
234
jsonObject.forEach { (key, value) ->
235
put(key, value)
236
}
237
// Add metadata
238
put("version", "1.0")
239
put("serializedAt", System.currentTimeMillis())
240
}
241
}
242
243
override fun transformDeserialize(element: JsonElement): JsonElement {
244
val jsonObject = element.jsonObject
245
return buildJsonObject {
246
// Copy only the fields we want, ignoring metadata
247
put("id", jsonObject["id"]!!)
248
put("name", jsonObject["name"]!!)
249
put("email", jsonObject["email"]!!)
250
// Ignore "version", "serializedAt", etc.
251
}
252
}
253
}
254
255
@Serializable(UserDataTransformer::class)
256
data class UserWithTransformer(
257
val id: Int,
258
val name: String,
259
val email: String
260
)
261
262
val user = UserWithTransformer(1, "Alice", "alice@example.com")
263
val json = Json.encodeToString(user)
264
// Result: {"id":1,"name":"Alice","email":"alice@example.com","version":"1.0","serializedAt":1234567890}
265
266
val decoded = Json.decodeFromString<UserWithTransformer>(json)
267
// Metadata fields are stripped during deserialization
268
```
269
270
### Advanced Transformer Examples
271
272
**Field Renaming Transformer:**
273
274
```kotlin
275
import kotlinx.serialization.*
276
import kotlinx.serialization.json.*
277
278
@Serializable
279
data class InternalUser(val userId: Int, val userName: String, val userEmail: String)
280
281
// Transform between internal field names and API field names
282
object ApiFieldTransformer : JsonTransformingSerializer<InternalUser>(InternalUser.serializer()) {
283
284
private val fieldMapping = mapOf(
285
"userId" to "id",
286
"userName" to "name",
287
"userEmail" to "email"
288
)
289
290
private val reverseMapping = fieldMapping.entries.associate { (k, v) -> v to k }
291
292
override fun transformSerialize(element: JsonElement): JsonElement {
293
val jsonObject = element.jsonObject
294
return buildJsonObject {
295
jsonObject.forEach { (key, value) ->
296
val apiKey = fieldMapping[key] ?: key
297
put(apiKey, value)
298
}
299
}
300
}
301
302
override fun transformDeserialize(element: JsonElement): JsonElement {
303
val jsonObject = element.jsonObject
304
return buildJsonObject {
305
jsonObject.forEach { (key, value) ->
306
val internalKey = reverseMapping[key] ?: key
307
put(internalKey, value)
308
}
309
}
310
}
311
}
312
```
313
314
**Nested Structure Transformer:**
315
316
```kotlin
317
import kotlinx.serialization.*
318
import kotlinx.serialization.json.*
319
320
@Serializable
321
data class FlatData(val name: String, val street: String, val city: String, val zip: String)
322
323
// Transform between flat structure and nested structure
324
object NestedTransformer : JsonTransformingSerializer<FlatData>(FlatData.serializer()) {
325
326
override fun transformSerialize(element: JsonElement): JsonElement {
327
val obj = element.jsonObject
328
return buildJsonObject {
329
put("name", obj["name"]!!)
330
putJsonObject("address") {
331
put("street", obj["street"]!!)
332
put("city", obj["city"]!!)
333
put("zip", obj["zip"]!!)
334
}
335
}
336
}
337
338
override fun transformDeserialize(element: JsonElement): JsonElement {
339
val obj = element.jsonObject
340
val address = obj["address"]?.jsonObject
341
342
return buildJsonObject {
343
put("name", obj["name"]!!)
344
if (address != null) {
345
put("street", address["street"]!!)
346
put("city", address["city"]!!)
347
put("zip", address["zip"]!!)
348
}
349
}
350
}
351
}
352
353
// Usage transforms:
354
// {"name":"John","street":"123 Main St","city":"Anytown","zip":"12345"}
355
// <->
356
// {"name":"John","address":{"street":"123 Main St","city":"Anytown","zip":"12345"}}
357
```
358
359
### Custom Serializer Registration
360
361
Register custom serializers with the Json configuration.
362
363
```kotlin
364
import kotlinx.serialization.*
365
import kotlinx.serialization.json.*
366
import kotlinx.serialization.modules.*
367
368
// Custom URL serializer
369
object UrlSerializer : KSerializer<java.net.URL> {
370
override val descriptor = PrimitiveSerialDescriptor("URL", PrimitiveKind.STRING)
371
override fun serialize(encoder: Encoder, value: java.net.URL) = encoder.encodeString(value.toString())
372
override fun deserialize(decoder: Decoder): java.net.URL = java.net.URL(decoder.decodeString())
373
}
374
375
// Custom UUID serializer with JsonEncoder optimization
376
object UuidSerializer : KSerializer<java.util.UUID> {
377
override val descriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
378
379
override fun serialize(encoder: Encoder, value: java.util.UUID) {
380
if (encoder is JsonEncoder) {
381
// Optimize for JSON by storing as compact object
382
encoder.encodeJsonElement(buildJsonObject {
383
put("uuid", value.toString())
384
put("type", "uuid")
385
})
386
} else {
387
encoder.encodeString(value.toString())
388
}
389
}
390
391
override fun deserialize(decoder: Decoder): java.util.UUID {
392
return if (decoder is JsonDecoder) {
393
val element = decoder.decodeJsonElement()
394
when {
395
element is JsonPrimitive -> java.util.UUID.fromString(element.content)
396
element is JsonObject && "uuid" in element ->
397
java.util.UUID.fromString(element["uuid"]!!.jsonPrimitive.content)
398
else -> throw SerializationException("Invalid UUID format")
399
}
400
} else {
401
java.util.UUID.fromString(decoder.decodeString())
402
}
403
}
404
}
405
406
// Configure Json with custom serializers
407
val customJson = Json {
408
serializersModule = SerializersModule {
409
contextual(java.net.URL::class, UrlSerializer)
410
contextual(java.util.UUID::class, UuidSerializer)
411
412
// Polymorphic serializers
413
polymorphic(ApiMessage::class) {
414
subclass(TextMessage::class)
415
subclass(ImageMessage::class)
416
default { ApiMessageSerializer }
417
}
418
}
419
}
420
421
@Serializable
422
data class ResourceInfo(
423
@Contextual val id: java.util.UUID,
424
@Contextual val location: java.net.URL,
425
val name: String
426
)
427
428
val resource = ResourceInfo(
429
java.util.UUID.randomUUID(),
430
java.net.URL("https://example.com/resource"),
431
"Example Resource"
432
)
433
434
val encoded = customJson.encodeToString(resource)
435
val decoded = customJson.decodeFromString<ResourceInfo>(encoded)
436
```
437
438
## Error Handling in Advanced Serialization
439
440
Advanced serializers should handle errors gracefully and provide meaningful error messages.
441
442
```kotlin
443
import kotlinx.serialization.*
444
import kotlinx.serialization.json.*
445
446
object SafeTransformer : JsonTransformingSerializer<MyData>(MyData.serializer()) {
447
override fun transformDeserialize(element: JsonElement): JsonElement {
448
return try {
449
// Attempt transformation
450
performTransformation(element)
451
} catch (e: Exception) {
452
// Provide context in error message
453
throw SerializationException(
454
"Failed to transform JSON element: ${e.message}. " +
455
"Element was: $element",
456
e
457
)
458
}
459
}
460
461
private fun performTransformation(element: JsonElement): JsonElement {
462
// Safe transformation logic with validation
463
require(element is JsonObject) { "Expected JsonObject, got ${element::class.simpleName}" }
464
465
val requiredFields = listOf("id", "name")
466
val missingFields = requiredFields.filter { it !in element }
467
if (missingFields.isNotEmpty()) {
468
throw IllegalArgumentException("Missing required fields: $missingFields")
469
}
470
471
return buildJsonObject {
472
element.forEach { (key, value) ->
473
put(key, value)
474
}
475
}
476
}
477
}
478
```
479
480
## Performance Considerations
481
482
- **JsonEncoder/JsonDecoder**: Provide direct access to JSON structure, avoiding intermediate object creation
483
- **JsonContentPolymorphicSerializer**: Content inspection has overhead; use sparingly for large datasets
484
- **JsonTransformingSerializer**: Each transformation creates new JsonElement instances; consider caching for repeated operations
485
- **Custom Serializers**: Implement efficiently to avoid performance bottlenecks in serialization-heavy applications