0
# Custom Serializers
1
2
Base classes and interfaces for implementing custom JSON serialization logic with transformation and polymorphic capabilities. These provide powerful extension points for handling complex serialization scenarios.
3
4
## Capabilities
5
6
### JsonTransformingSerializer
7
8
Abstract base class for creating serializers that transform JsonElement structures during serialization/deserialization.
9
10
```kotlin { .api }
11
/**
12
* Base class for JSON transformation serializers
13
* @param tSerializer The underlying serializer for type T
14
*/
15
abstract class JsonTransformingSerializer<T>(private val tSerializer: KSerializer<T>) : KSerializer<T> {
16
/**
17
* Transform JsonElement during serialization (encode)
18
* @param element JsonElement to transform
19
* @return Transformed JsonElement
20
*/
21
protected open fun transformSerialize(element: JsonElement): JsonElement = element
22
23
/**
24
* Transform JsonElement during deserialization (decode)
25
* @param element JsonElement to transform
26
* @return Transformed JsonElement
27
*/
28
protected open fun transformDeserialize(element: JsonElement): JsonElement = element
29
}
30
```
31
32
**Usage Examples:**
33
34
```kotlin
35
@Serializable
36
data class Coordinates(val x: Double, val y: Double)
37
38
// Transform coordinates between different coordinate systems
39
object CoordinateTransformer : JsonTransformingSerializer<Coordinates>(Coordinates.serializer()) {
40
override fun transformSerialize(element: JsonElement): JsonElement {
41
val obj = element.jsonObject
42
val x = obj["x"]?.jsonPrimitive?.double ?: 0.0
43
val y = obj["y"]?.jsonPrimitive?.double ?: 0.0
44
45
// Convert from internal coordinate system to API coordinate system
46
// Example: scale by 100 and offset
47
return buildJsonObject {
48
put("x", x * 100 + 1000)
49
put("y", y * 100 + 2000)
50
}
51
}
52
53
override fun transformDeserialize(element: JsonElement): JsonElement {
54
val obj = element.jsonObject
55
val x = obj["x"]?.jsonPrimitive?.double ?: 0.0
56
val y = obj["y"]?.jsonPrimitive?.double ?: 0.0
57
58
// Convert from API coordinate system to internal coordinate system
59
return buildJsonObject {
60
put("x", (x - 1000) / 100)
61
put("y", (y - 2000) / 100)
62
}
63
}
64
}
65
66
// Usage
67
val json = Json.Default
68
val coords = Coordinates(5.0, 10.0)
69
70
// Serialize with transformation
71
val serialized = json.encodeToString(CoordinateTransformer, coords)
72
// Result: {"x":1500.0,"y":3000.0} (transformed values)
73
74
// Deserialize with transformation
75
val apiCoords = """{"x":1500.0,"y":3000.0}"""
76
val deserialized = json.decodeFromString(CoordinateTransformer, apiCoords)
77
// Result: Coordinates(x=5.0, y=10.0) (original values restored)
78
```
79
80
### Property Manipulation Serializer
81
82
Transform property names or values during serialization.
83
84
**Usage Examples:**
85
86
```kotlin
87
@Serializable
88
data class User(val firstName: String, val lastName: String, val age: Int)
89
90
// Serializer that wraps user data in an envelope
91
object UserEnvelopeSerializer : JsonTransformingSerializer<User>(User.serializer()) {
92
override fun transformSerialize(element: JsonElement): JsonElement {
93
return buildJsonObject {
94
put("user_data", element)
95
put("metadata", buildJsonObject {
96
put("serialized_at", System.currentTimeMillis())
97
put("version", "1.0")
98
})
99
}
100
}
101
102
override fun transformDeserialize(element: JsonElement): JsonElement {
103
val obj = element.jsonObject
104
// Extract user data from envelope
105
return obj["user_data"] ?: obj // Fallback to original if no envelope
106
}
107
}
108
109
// Usage
110
val user = User("Alice", "Smith", 30)
111
val enveloped = json.encodeToString(UserEnvelopeSerializer, user)
112
// Result: {
113
// "user_data": {"firstName":"Alice","lastName":"Smith","age":30},
114
// "metadata": {"serialized_at":1234567890,"version":"1.0"}
115
// }
116
117
val restored = json.decodeFromString(UserEnvelopeSerializer, enveloped)
118
// Result: User(firstName="Alice", lastName="Smith", age=30)
119
```
120
121
### JsonContentPolymorphicSerializer
122
123
Abstract class for polymorphic serialization based on JSON content inspection.
124
125
```kotlin { .api }
126
/**
127
* Polymorphic serializer that selects implementation based on JSON content
128
* @param baseClass Base class for polymorphism
129
*/
130
abstract class JsonContentPolymorphicSerializer<T : Any>(private val baseClass: KClass<T>) : AbstractPolymorphicSerializer<T>() {
131
/**
132
* Select deserializer based on JsonElement content
133
* @param element JsonElement to inspect
134
* @return Deserialization strategy for the specific type
135
*/
136
protected abstract fun selectDeserializer(element: JsonElement): DeserializationStrategy<T>
137
}
138
```
139
140
**Usage Examples:**
141
142
```kotlin
143
@Serializable
144
abstract class Shape {
145
abstract val area: Double
146
}
147
148
@Serializable
149
data class Circle(val radius: Double) : Shape() {
150
override val area: Double get() = 3.14159 * radius * radius
151
}
152
153
@Serializable
154
data class Rectangle(val width: Double, val height: Double) : Shape() {
155
override val area: Double get() = width * height
156
}
157
158
@Serializable
159
data class Triangle(val base: Double, val height: Double) : Shape() {
160
override val area: Double get() = 0.5 * base * height
161
}
162
163
// Content-based polymorphic serializer
164
object ShapeSerializer : JsonContentPolymorphicSerializer<Shape>(Shape::class) {
165
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Shape> {
166
val obj = element.jsonObject
167
return when {
168
"radius" in obj -> Circle.serializer()
169
"width" in obj && "height" in obj -> Rectangle.serializer()
170
"base" in obj && "height" in obj -> Triangle.serializer()
171
else -> throw JsonDecodingException("Unknown shape type: ${obj.keys}")
172
}
173
}
174
}
175
176
// Usage
177
val shapes = listOf(
178
Circle(5.0),
179
Rectangle(4.0, 6.0),
180
Triangle(3.0, 8.0)
181
)
182
183
val json = Json.Default
184
185
// Serialize different shapes
186
shapes.forEach { shape ->
187
val serialized = json.encodeToString(ShapeSerializer, shape)
188
println("Serialized: $serialized")
189
190
val deserialized = json.decodeFromString(ShapeSerializer, serialized)
191
println("Deserialized: $deserialized")
192
println("Area: ${deserialized.area}")
193
println()
194
}
195
196
// Handle JSON without discriminator
197
val circleJson = """{"radius":10.0}"""
198
val circle = json.decodeFromString(ShapeSerializer, circleJson)
199
// Result: Circle(radius=10.0)
200
201
val rectangleJson = """{"width":5.0,"height":3.0}"""
202
val rectangle = json.decodeFromString(ShapeSerializer, rectangleJson)
203
// Result: Rectangle(width=5.0, height=3.0)
204
```
205
206
### JsonEncoder and JsonDecoder Interfaces
207
208
Access JSON-specific encoding and decoding capabilities in custom serializers.
209
210
```kotlin { .api }
211
/**
212
* JSON-specific encoder interface
213
*/
214
interface JsonEncoder : Encoder, CompositeEncoder {
215
/**
216
* Json instance being used for encoding
217
*/
218
val json: Json
219
220
/**
221
* Encode JsonElement directly
222
* @param element JsonElement to encode
223
*/
224
fun encodeJsonElement(element: JsonElement)
225
}
226
227
/**
228
* JSON-specific decoder interface
229
*/
230
interface JsonDecoder : Decoder, CompositeDecoder {
231
/**
232
* Json instance being used for decoding
233
*/
234
val json: Json
235
236
/**
237
* Decode current value as JsonElement
238
* @return JsonElement representation
239
*/
240
fun decodeJsonElement(): JsonElement
241
}
242
```
243
244
**Usage Examples:**
245
246
```kotlin
247
@Serializable
248
data class FlexibleData(val content: String)
249
250
// Custom serializer using JsonEncoder/JsonDecoder
251
object FlexibleDataSerializer : KSerializer<FlexibleData> {
252
override val descriptor = buildClassSerialDescriptor("FlexibleData") {
253
element<String>("content")
254
}
255
256
override fun serialize(encoder: Encoder, value: FlexibleData) {
257
if (encoder is JsonEncoder) {
258
// Use JSON-specific functionality
259
val element = buildJsonObject {
260
put("content", value.content)
261
put("serialized_with", "custom_serializer")
262
put("timestamp", System.currentTimeMillis())
263
}
264
encoder.encodeJsonElement(element)
265
} else {
266
// Fallback for non-JSON formats
267
encoder.encodeString(value.content)
268
}
269
}
270
271
override fun deserialize(decoder: Decoder): FlexibleData {
272
return if (decoder is JsonDecoder) {
273
// Use JSON-specific functionality
274
val element = decoder.decodeJsonElement()
275
val obj = element.jsonObject
276
val content = obj["content"]?.jsonPrimitive?.content
277
?: throw JsonDecodingException("Missing content field")
278
FlexibleData(content)
279
} else {
280
// Fallback for non-JSON formats
281
FlexibleData(decoder.decodeString())
282
}
283
}
284
}
285
286
// Usage
287
val data = FlexibleData("Hello, World!")
288
val json = Json { prettyPrint = true }
289
290
val serialized = json.encodeToString(FlexibleDataSerializer, data)
291
// Result: {
292
// "content": "Hello, World!",
293
// "serialized_with": "custom_serializer",
294
// "timestamp": 1234567890
295
// }
296
297
val deserialized = json.decodeFromString(FlexibleDataSerializer, serialized)
298
// Result: FlexibleData(content="Hello, World!")
299
```
300
301
### Contextual Serialization
302
303
Access serialization context and configuration in custom serializers.
304
305
**Usage Examples:**
306
307
```kotlin
308
// Serializer that adapts behavior based on Json configuration
309
object AdaptiveStringSerializer : KSerializer<String> {
310
override val descriptor = PrimitiveSerialDescriptor("AdaptiveString", PrimitiveKind.STRING)
311
312
override fun serialize(encoder: Encoder, value: String) {
313
if (encoder is JsonEncoder) {
314
val json = encoder.json
315
val element = if (json.configuration.prettyPrint) {
316
// If pretty printing is enabled, add formatting hints
317
JsonPrimitive("FORMATTED: $value")
318
} else {
319
JsonPrimitive(value)
320
}
321
encoder.encodeJsonElement(element)
322
} else {
323
encoder.encodeString(value)
324
}
325
}
326
327
override fun deserialize(decoder: Decoder): String {
328
return if (decoder is JsonDecoder) {
329
val element = decoder.decodeJsonElement()
330
val content = element.jsonPrimitive.content
331
// Remove formatting prefix if present
332
content.removePrefix("FORMATTED: ")
333
} else {
334
decoder.decodeString()
335
}
336
}
337
}
338
339
// Usage with different Json configurations
340
val compactJson = Json.Default
341
val prettyJson = Json { prettyPrint = true }
342
343
val text = "Hello"
344
345
val compactResult = compactJson.encodeToString(AdaptiveStringSerializer, text)
346
// Result: "Hello"
347
348
val prettyResult = prettyJson.encodeToString(AdaptiveStringSerializer, text)
349
// Result: "FORMATTED: Hello"
350
351
// Both deserialize to the same value
352
val restored1 = compactJson.decodeFromString(AdaptiveStringSerializer, compactResult)
353
val restored2 = prettyJson.decodeFromString(AdaptiveStringSerializer, prettyResult)
354
// Both result in: "Hello"
355
```
356
357
### Error Handling in Custom Serializers
358
359
Proper error handling patterns for custom serializers.
360
361
**Usage Examples:**
362
363
```kotlin
364
object SafeIntSerializer : KSerializer<Int> {
365
override val descriptor = PrimitiveSerialDescriptor("SafeInt", PrimitiveKind.INT)
366
367
override fun serialize(encoder: Encoder, value: Int) {
368
encoder.encodeInt(value)
369
}
370
371
override fun deserialize(decoder: Decoder): Int {
372
return try {
373
if (decoder is JsonDecoder) {
374
val element = decoder.decodeJsonElement()
375
when (val primitive = element.jsonPrimitive) {
376
is JsonNull -> 0 // Default value for null
377
else -> {
378
// Try to parse as int, with fallback handling
379
primitive.intOrNull
380
?: primitive.doubleOrNull?.toInt()
381
?: primitive.content.toIntOrNull()
382
?: throw JsonDecodingException("Cannot convert '${primitive.content}' to Int")
383
}
384
}
385
} else {
386
decoder.decodeInt()
387
}
388
} catch (e: NumberFormatException) {
389
throw JsonDecodingException("Invalid number format: ${e.message}")
390
} catch (e: IllegalArgumentException) {
391
throw JsonDecodingException("Invalid integer value: ${e.message}")
392
}
393
}
394
}
395
396
// Usage - handles various input formats gracefully
397
val json = Json.Default
398
399
val validInputs = listOf(
400
"42", // String number
401
"42.0", // Float that converts to int
402
"null" // Null converts to 0
403
)
404
405
validInputs.forEach { input ->
406
try {
407
val result = json.decodeFromString(SafeIntSerializer, input)
408
println("'$input' -> $result")
409
} catch (e: JsonDecodingException) {
410
println("'$input' -> Error: ${e.message}")
411
}
412
}
413
```