0
# Serialization Support
1
2
Comprehensive kotlinx.serialization support with multiple serializer types for different use cases. The library provides default serializers, explicit ISO 8601 serializers, component-based serializers, and specialized serializers for different data formats.
3
4
## Capabilities
5
6
### Default Serializers
7
8
Standard serializers that delegate to the `toString()` and `parse()` methods of each type, providing automatic ISO 8601 compliance.
9
10
#### Core Type Serializers
11
12
```kotlin { .api }
13
/**
14
* Default serializer for kotlin.time.Instant
15
* Uses ISO 8601 format via toString/parse delegation
16
*/
17
object InstantSerializer : KSerializer<Instant>
18
19
/**
20
* Default serializer for LocalDate
21
* Uses ISO 8601 extended format (YYYY-MM-DD)
22
*/
23
object LocalDateSerializer : KSerializer<LocalDate>
24
25
/**
26
* Default serializer for LocalTime
27
* Uses ISO 8601 extended format (HH:MM:SS[.fff])
28
*/
29
object LocalTimeSerializer : KSerializer<LocalTime>
30
31
/**
32
* Default serializer for LocalDateTime
33
* Uses ISO 8601 extended format (YYYY-MM-DDTHH:MM:SS[.fff])
34
*/
35
object LocalDateTimeSerializer : KSerializer<LocalDateTime>
36
37
/**
38
* Default serializer for YearMonth
39
* Uses ISO 8601 format (YYYY-MM)
40
*/
41
object YearMonthSerializer : KSerializer<YearMonth>
42
43
/**
44
* Default serializer for UtcOffset
45
* Uses ISO 8601 format (+HH:MM, Z, etc.)
46
*/
47
object UtcOffsetSerializer : KSerializer<UtcOffset>
48
```
49
50
#### Time Zone Serializers
51
52
```kotlin { .api }
53
/**
54
* Default serializer for TimeZone
55
* Serializes the time zone ID as a string
56
*/
57
object TimeZoneSerializer : KSerializer<TimeZone>
58
59
/**
60
* Default serializer for FixedOffsetTimeZone
61
* Serializes as the offset string representation
62
*/
63
object FixedOffsetTimeZoneSerializer : KSerializer<FixedOffsetTimeZone>
64
```
65
66
#### Period and Unit Serializers
67
68
```kotlin { .api }
69
/**
70
* Default serializer for DateTimePeriod
71
* Uses ISO 8601 duration format via toString/parse
72
*/
73
object DateTimePeriodSerializer : KSerializer<DateTimePeriod>
74
75
/**
76
* Default serializer for DatePeriod
77
* Uses ISO 8601 date duration format via toString/parse
78
*/
79
object DatePeriodSerializer : KSerializer<DatePeriod>
80
81
/**
82
* Default serializer for DateTimeUnit
83
* Serializes unit representation
84
*/
85
object DateTimeUnitSerializer : KSerializer<DateTimeUnit>
86
```
87
88
#### Enumeration Serializers
89
90
```kotlin { .api }
91
/**
92
* Default serializer for Month enum
93
* Serializes as enum name
94
*/
95
object MonthSerializer : KSerializer<Month>
96
97
/**
98
* Default serializer for DayOfWeek enum
99
* Serializes as enum name
100
*/
101
object DayOfWeekSerializer : KSerializer<DayOfWeek>
102
```
103
104
**Usage Examples:**
105
106
```kotlin
107
import kotlinx.datetime.*
108
import kotlinx.serialization.*
109
import kotlinx.serialization.json.*
110
111
@Serializable
112
data class Event(
113
val name: String,
114
val date: LocalDate,
115
val time: LocalTime,
116
val timeZone: TimeZone,
117
val duration: DateTimePeriod
118
)
119
120
val event = Event(
121
name = "Conference",
122
date = LocalDate(2023, 12, 25),
123
time = LocalTime(15, 30),
124
timeZone = TimeZone.of("America/New_York"),
125
duration = DateTimePeriod(hours = 2, minutes = 30)
126
)
127
128
// Serialize to JSON using default serializers
129
val json = Json.encodeToString(event)
130
println(json)
131
// {"name":"Conference","date":"2023-12-25","time":"15:30:00","timeZone":"America/New_York","duration":"PT2H30M"}
132
133
// Deserialize from JSON
134
val restored = Json.decodeFromString<Event>(json)
135
println("Restored: $restored")
136
```
137
138
### ISO 8601 Serializers
139
140
Explicit ISO 8601 serializers that guarantee strict ISO compliance regardless of default `toString()` implementation.
141
142
```kotlin { .api }
143
/**
144
* Explicit ISO 8601 serializer for kotlin.time.Instant
145
* Guarantees ISO 8601 format regardless of toString implementation
146
*/
147
object InstantIso8601Serializer : KSerializer<Instant>
148
149
/**
150
* Explicit ISO 8601 serializer for LocalDate
151
* Uses ISO 8601 extended format (YYYY-MM-DD)
152
*/
153
object LocalDateIso8601Serializer : KSerializer<LocalDate>
154
155
/**
156
* Explicit ISO 8601 serializer for LocalTime
157
* Uses ISO 8601 extended format with optional fractions
158
*/
159
object LocalTimeIso8601Serializer : KSerializer<LocalTime>
160
161
/**
162
* Explicit ISO 8601 serializer for LocalDateTime
163
* Uses ISO 8601 extended format
164
*/
165
object LocalDateTimeIso8601Serializer : KSerializer<LocalDateTime>
166
167
/**
168
* Explicit ISO 8601 serializer for YearMonth
169
* Uses ISO 8601 format (YYYY-MM)
170
*/
171
object YearMonthIso8601Serializer : KSerializer<YearMonth>
172
173
/**
174
* Explicit ISO 8601 serializer for UtcOffset
175
* Uses ISO 8601 offset format
176
*/
177
object UtcOffsetIso8601Serializer : KSerializer<UtcOffset>
178
179
/**
180
* Explicit ISO 8601 serializer for DateTimePeriod
181
* Uses ISO 8601 duration format
182
*/
183
object DateTimePeriodIso8601Serializer : KSerializer<DateTimePeriod>
184
185
/**
186
* Explicit ISO 8601 serializer for DatePeriod
187
* Uses ISO 8601 date duration format
188
*/
189
object DatePeriodIso8601Serializer : KSerializer<DatePeriod>
190
```
191
192
**Usage Examples:**
193
194
```kotlin
195
import kotlinx.datetime.*
196
import kotlinx.serialization.*
197
import kotlinx.serialization.json.*
198
199
@Serializable
200
data class StrictEvent(
201
val name: String,
202
@Serializable(with = LocalDateIso8601Serializer::class)
203
val date: LocalDate,
204
@Serializable(with = LocalTimeIso8601Serializer::class)
205
val time: LocalTime,
206
@Serializable(with = DateTimePeriodIso8601Serializer::class)
207
val duration: DateTimePeriod
208
)
209
210
val event = StrictEvent(
211
name = "Meeting",
212
date = LocalDate(2023, 12, 25),
213
time = LocalTime(15, 30, 45, 123456789),
214
duration = DateTimePeriod(hours = 1, minutes = 30)
215
)
216
217
val json = Json.encodeToString(event)
218
println(json)
219
// {"name":"Meeting","date":"2023-12-25","time":"15:30:45.123456789","duration":"PT1H30M"}
220
```
221
222
### Component Serializers
223
224
Serializers that represent date/time values as JSON objects with individual component fields.
225
226
```kotlin { .api }
227
/**
228
* Component serializer for LocalDate
229
* Serializes as object: {"year": 2023, "month": 12, "day": 25}
230
*/
231
object LocalDateComponentSerializer : KSerializer<LocalDate>
232
233
/**
234
* Component serializer for LocalTime
235
* Serializes as object: {"hour": 15, "minute": 30, "second": 45, "nanosecond": 123456789}
236
*/
237
object LocalTimeComponentSerializer : KSerializer<LocalTime>
238
239
/**
240
* Component serializer for LocalDateTime
241
* Serializes as object with separate date and time components
242
*/
243
object LocalDateTimeComponentSerializer : KSerializer<LocalDateTime>
244
245
/**
246
* Component serializer for YearMonth
247
* Serializes as object: {"year": 2023, "month": 12}
248
*/
249
object YearMonthComponentSerializer : KSerializer<YearMonth>
250
251
/**
252
* Component serializer for DateTimePeriod
253
* Serializes as object with all period components
254
*/
255
object DateTimePeriodComponentSerializer : KSerializer<DateTimePeriod>
256
257
/**
258
* Component serializer for DatePeriod
259
* Serializes as object: {"years": 1, "months": 6, "days": 15}
260
*/
261
object DatePeriodComponentSerializer : KSerializer<DatePeriod>
262
```
263
264
**Usage Examples:**
265
266
```kotlin
267
import kotlinx.datetime.*
268
import kotlinx.serialization.*
269
import kotlinx.serialization.json.*
270
271
@Serializable
272
data class ComponentEvent(
273
val name: String,
274
@Serializable(with = LocalDateComponentSerializer::class)
275
val date: LocalDate,
276
@Serializable(with = LocalTimeComponentSerializer::class)
277
val time: LocalTime,
278
@Serializable(with = DatePeriodComponentSerializer::class)
279
val duration: DatePeriod
280
)
281
282
val event = ComponentEvent(
283
name = "Workshop",
284
date = LocalDate(2023, 12, 25),
285
time = LocalTime(15, 30, 45),
286
duration = DatePeriod(days = 2)
287
)
288
289
val json = Json { prettyPrint = true }.encodeToString(event)
290
println(json)
291
/*
292
{
293
"name": "Workshop",
294
"date": {
295
"year": 2023,
296
"month": 12,
297
"day": 25
298
},
299
"time": {
300
"hour": 15,
301
"minute": 30,
302
"second": 45,
303
"nanosecond": 0
304
},
305
"duration": {
306
"years": 0,
307
"months": 0,
308
"days": 2
309
}
310
}
311
*/
312
313
// Deserialize back to objects
314
val restored = Json.decodeFromString<ComponentEvent>(json)
315
println("Restored date: ${restored.date}") // 2023-12-25
316
```
317
318
### Specialized DateTimeUnit Serializers
319
320
Serializers for different DateTimeUnit types based on their classification.
321
322
```kotlin { .api }
323
/**
324
* Serializer for TimeBased DateTimeUnit
325
* Handles units with nanosecond precision
326
*/
327
object TimeBasedDateTimeUnitSerializer : KSerializer<DateTimeUnit.TimeBased>
328
329
/**
330
* Serializer for DayBased DateTimeUnit
331
* Handles day and week units
332
*/
333
object DayBasedDateTimeUnitSerializer : KSerializer<DateTimeUnit.DayBased>
334
335
/**
336
* Serializer for MonthBased DateTimeUnit
337
* Handles month, quarter, year, and century units
338
*/
339
object MonthBasedDateTimeUnitSerializer : KSerializer<DateTimeUnit.MonthBased>
340
341
/**
342
* Serializer for DateBased DateTimeUnit (base class)
343
* Handles all date-based units
344
*/
345
object DateBasedDateTimeUnitSerializer : KSerializer<DateTimeUnit.DateBased>
346
```
347
348
**Usage Examples:**
349
350
```kotlin
351
import kotlinx.datetime.*
352
import kotlinx.serialization.*
353
import kotlinx.serialization.json.*
354
355
@Serializable
356
data class ScheduleRule(
357
val name: String,
358
@Serializable(with = TimeBasedDateTimeUnitSerializer::class)
359
val timeUnit: DateTimeUnit.TimeBased,
360
@Serializable(with = MonthBasedDateTimeUnitSerializer::class)
361
val dateUnit: DateTimeUnit.MonthBased,
362
val interval: Int
363
)
364
365
val rule = ScheduleRule(
366
name = "Quarterly Review",
367
timeUnit = DateTimeUnit.HOUR,
368
dateUnit = DateTimeUnit.QUARTER,
369
interval = 1
370
)
371
372
val json = Json.encodeToString(rule)
373
println(json)
374
// Exact format depends on implementation - could be unit names or nanosecond values
375
```
376
377
## Advanced Serialization Patterns
378
379
### Custom Serialization Context
380
381
```kotlin
382
import kotlinx.datetime.*
383
import kotlinx.serialization.*
384
import kotlinx.serialization.json.*
385
import kotlinx.serialization.modules.*
386
387
// Custom serializer that includes time zone information
388
@Serializer(forClass = LocalDateTime::class)
389
object LocalDateTimeWithZoneSerializer : KSerializer<LocalDateTime> {
390
override val descriptor = buildClassSerialDescriptor("LocalDateTimeWithZone") {
391
element<String>("dateTime")
392
element<String>("timeZone")
393
}
394
395
override fun serialize(encoder: Encoder, value: LocalDateTime) {
396
encoder.encodeStructure(descriptor) {
397
encodeStringElement(descriptor, 0, value.toString())
398
// Would need context to get time zone - this is a conceptual example
399
encodeStringElement(descriptor, 1, "UTC")
400
}
401
}
402
403
override fun deserialize(decoder: Decoder): LocalDateTime {
404
return decoder.decodeStructure(descriptor) {
405
var dateTime: String? = null
406
var timeZone: String? = null
407
408
while (true) {
409
when (val index = decodeElementIndex(descriptor)) {
410
0 -> dateTime = decodeStringElement(descriptor, 0)
411
1 -> timeZone = decodeStringElement(descriptor, 1)
412
CompositeDecoder.DECODE_DONE -> break
413
else -> error("Unexpected index: $index")
414
}
415
}
416
417
LocalDateTime.parse(dateTime!!)
418
}
419
}
420
}
421
422
// Using custom module
423
val customModule = SerializersModule {
424
contextual(LocalDateTime::class, LocalDateTimeWithZoneSerializer)
425
}
426
427
val json = Json { serializersModule = customModule }
428
```
429
430
### Conditional Serialization
431
432
```kotlin
433
import kotlinx.datetime.*
434
import kotlinx.serialization.*
435
import kotlinx.serialization.json.*
436
437
@Serializable
438
data class FlexibleEvent(
439
val name: String,
440
val date: LocalDate,
441
val time: LocalTime? = null, // Optional time
442
@Serializable(with = DateTimePeriodSerializer::class)
443
val duration: DateTimePeriod? = null // Optional duration
444
) {
445
// Custom property for all-day events
446
val isAllDay: Boolean get() = time == null
447
448
// Convert to full datetime if time is present
449
val dateTime: LocalDateTime? get() = time?.let { date.atTime(it) }
450
}
451
452
val allDayEvent = FlexibleEvent(
453
name = "Holiday",
454
date = LocalDate(2023, 12, 25)
455
)
456
457
val timedEvent = FlexibleEvent(
458
name = "Meeting",
459
date = LocalDate(2023, 12, 25),
460
time = LocalTime(15, 30),
461
duration = DateTimePeriod(hours = 2)
462
)
463
464
// Both serialize differently
465
println("All-day: ${Json.encodeToString(allDayEvent)}")
466
println("Timed: ${Json.encodeToString(timedEvent)}")
467
```
468
469
### Polymorphic Serialization
470
471
```kotlin
472
import kotlinx.datetime.*
473
import kotlinx.serialization.*
474
import kotlinx.serialization.json.*
475
476
@Serializable
477
sealed class TimeSpan {
478
@Serializable
479
@SerialName("duration")
480
data class DurationSpan(
481
@Serializable(with = DateTimePeriodSerializer::class)
482
val period: DateTimePeriod
483
) : TimeSpan()
484
485
@Serializable
486
@SerialName("range")
487
data class DateRangeSpan(
488
val start: LocalDate,
489
val end: LocalDate
490
) : TimeSpan()
491
492
@Serializable
493
@SerialName("recurring")
494
data class RecurringSpan(
495
val start: LocalDate,
496
@Serializable(with = DateTimePeriodSerializer::class)
497
val interval: DateTimePeriod,
498
val count: Int
499
) : TimeSpan()
500
}
501
502
@Serializable
503
data class Task(
504
val name: String,
505
val timeSpan: TimeSpan
506
)
507
508
val tasks = listOf(
509
Task("Quick task", TimeSpan.DurationSpan(DateTimePeriod(hours = 1))),
510
Task("Project", TimeSpan.DateRangeSpan(
511
LocalDate(2023, 12, 1),
512
LocalDate(2023, 12, 31)
513
)),
514
Task("Weekly meeting", TimeSpan.RecurringSpan(
515
LocalDate(2023, 12, 1),
516
DateTimePeriod(days = 7),
517
count = 4
518
))
519
)
520
521
val json = Json { prettyPrint = true }
522
tasks.forEach { task ->
523
println("${task.name}:")
524
println(json.encodeToString(task))
525
println()
526
}
527
```
528
529
### Database Serialization
530
531
```kotlin
532
import kotlinx.datetime.*
533
import kotlinx.serialization.*
534
535
// Example adapter for database storage
536
class DateTimeSerializationAdapter {
537
538
// Convert to database-friendly formats
539
fun localDateToLong(date: LocalDate): Long = date.toEpochDays()
540
fun longToLocalDate(epochDays: Long): LocalDate = LocalDate.fromEpochDays(epochDays)
541
542
fun instantToLong(instant: Instant): Long = instant.epochSeconds
543
fun longToInstant(epochSeconds: Long): Instant = Instant.fromEpochSeconds(epochSeconds)
544
545
fun timeZoneToString(timeZone: TimeZone): String = timeZone.id
546
fun stringToTimeZone(id: String): TimeZone = TimeZone.of(id)
547
548
fun periodToString(period: DateTimePeriod): String = period.toString()
549
fun stringToPeriod(str: String): DateTimePeriod = DateTimePeriod.parse(str)
550
}
551
552
// Usage in data classes for database entities
553
@Serializable
554
data class DatabaseEvent(
555
val id: Long,
556
val name: String,
557
558
// Store as epoch days for efficient database operations
559
@Serializable(with = EpochDaysSerializer::class)
560
val date: LocalDate,
561
562
// Store as epoch seconds
563
@Serializable(with = EpochSecondsSerializer::class)
564
val createdAt: Instant,
565
566
// Store time zone as string ID
567
val timeZoneId: String
568
) {
569
// Computed property for actual time zone
570
val timeZone: TimeZone get() = TimeZone.of(timeZoneId)
571
}
572
573
// Custom serializers for database storage
574
object EpochDaysSerializer : KSerializer<LocalDate> {
575
override val descriptor = PrimitiveSerialDescriptor("EpochDays", PrimitiveKind.LONG)
576
577
override fun serialize(encoder: Encoder, value: LocalDate) {
578
encoder.encodeLong(value.toEpochDays())
579
}
580
581
override fun deserialize(decoder: Decoder): LocalDate {
582
return LocalDate.fromEpochDays(decoder.decodeLong())
583
}
584
}
585
586
object EpochSecondsSerializer : KSerializer<Instant> {
587
override val descriptor = PrimitiveSerialDescriptor("EpochSeconds", PrimitiveKind.LONG)
588
589
override fun serialize(encoder: Encoder, value: Instant) {
590
encoder.encodeLong(value.epochSeconds)
591
}
592
593
override fun deserialize(decoder: Decoder): Instant {
594
return Instant.fromEpochSeconds(decoder.decodeLong())
595
}
596
}
597
```
598
599
## Migration and Compatibility
600
601
### Handling Format Changes
602
603
```kotlin
604
import kotlinx.datetime.*
605
import kotlinx.serialization.*
606
import kotlinx.serialization.json.*
607
608
// Versioned serialization for backward compatibility
609
@Serializable
610
data class EventV1(
611
val name: String,
612
val dateString: String // Old format: stored as string
613
)
614
615
@Serializable
616
data class EventV2(
617
val name: String,
618
val date: LocalDate, // New format: proper LocalDate
619
val version: Int = 2
620
)
621
622
// Migration serializer
623
object EventMigrationSerializer : KSerializer<EventV2> {
624
override val descriptor = EventV2.serializer().descriptor
625
626
override fun serialize(encoder: Encoder, value: EventV2) {
627
EventV2.serializer().serialize(encoder, value)
628
}
629
630
override fun deserialize(decoder: Decoder): EventV2 {
631
val element = decoder.decodeSerializableValue(JsonElement.serializer())
632
val jsonObject = element.jsonObject
633
634
return when {
635
"version" in jsonObject -> {
636
// New format
637
Json.decodeFromJsonElement(EventV2.serializer(), element)
638
}
639
"dateString" in jsonObject -> {
640
// Old format - migrate
641
val v1 = Json.decodeFromJsonElement(EventV1.serializer(), element)
642
EventV2(
643
name = v1.name,
644
date = LocalDate.parse(v1.dateString) // Convert string to LocalDate
645
)
646
}
647
else -> error("Unknown event format")
648
}
649
}
650
}
651
652
// Usage
653
val oldJson = """{"name":"Meeting","dateString":"2023-12-25"}"""
654
val newJson = """{"name":"Conference","date":"2023-12-31","version":2}"""
655
656
val event1 = Json.decodeFromString(EventMigrationSerializer, oldJson)
657
val event2 = Json.decodeFromString(EventMigrationSerializer, newJson)
658
659
println("Migrated: $event1") // EventV2 with LocalDate
660
println("Current: $event2") // EventV2 as-is
661
```
662
663
### Error Handling and Validation
664
665
```kotlin
666
import kotlinx.datetime.*
667
import kotlinx.serialization.*
668
import kotlinx.serialization.json.*
669
670
// Robust serializer with validation
671
object ValidatedLocalDateSerializer : KSerializer<LocalDate> {
672
override val descriptor = PrimitiveSerialDescriptor("ValidatedLocalDate", PrimitiveKind.STRING)
673
674
override fun serialize(encoder: Encoder, value: LocalDate) {
675
encoder.encodeString(value.toString())
676
}
677
678
override fun deserialize(decoder: Decoder): LocalDate {
679
val dateString = decoder.decodeString()
680
681
return try {
682
LocalDate.parse(dateString)
683
} catch (e: Exception) {
684
// Try alternative formats or provide default
685
when {
686
dateString.matches(Regex("""\d{8}""")) -> {
687
// Handle YYYYMMDD format
688
val year = dateString.substring(0, 4).toInt()
689
val month = dateString.substring(4, 6).toInt()
690
val day = dateString.substring(6, 8).toInt()
691
LocalDate(year, month, day)
692
}
693
else -> throw SerializationException("Invalid date format: $dateString", e)
694
}
695
}
696
}
697
}
698
699
// Safe parsing wrapper
700
inline fun <reified T> safeJsonDecode(json: String): Result<T> {
701
return try {
702
Result.success(Json.decodeFromString<T>(json))
703
} catch (e: SerializationException) {
704
Result.failure(e)
705
}
706
}
707
708
// Usage
709
val validJson = """{"date":"2023-12-25"}"""
710
val invalidJson = """{"date":"invalid-date"}"""
711
val alternativeJson = """{"date":"20231225"}"""
712
713
@Serializable
714
data class DateContainer(@Serializable(with = ValidatedLocalDateSerializer::class) val date: LocalDate)
715
716
val result1 = safeJsonDecode<DateContainer>(validJson) // Success
717
val result2 = safeJsonDecode<DateContainer>(invalidJson) // Failure
718
val result3 = safeJsonDecode<DateContainer>(alternativeJson) // Success (handled YYYYMMDD)
719
720
result1.onSuccess { println("Valid: ${it.date}") }
721
result2.onFailure { println("Error: ${it.message}") }
722
result3.onSuccess { println("Alternative: ${it.date}") }
723
```