Skip to content

Commit f451df4

Browse files
authored
Rework datetime serializers to be more idiomatic (#308)
* Reduce the number of internal accesses where possible (KT-60866) * Use FQN of datetime types as serial descriptor names to distinguish them
1 parent 4e3de8f commit f451df4

10 files changed

+77
-84
lines changed

core/common/src/serializers/DateTimePeriodSerializers.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import kotlinx.serialization.encoding.*
1919
public object DateTimePeriodComponentSerializer: KSerializer<DateTimePeriod> {
2020

2121
override val descriptor: SerialDescriptor =
22-
buildClassSerialDescriptor("DateTimePeriod") {
22+
buildClassSerialDescriptor("kotlinx.datetime.DateTimePeriod") {
2323
element<Int>("years", isOptional = true)
2424
element<Int>("months", isOptional = true)
2525
element<Int>("days", isOptional = true)
@@ -81,7 +81,7 @@ public object DateTimePeriodComponentSerializer: KSerializer<DateTimePeriod> {
8181
public object DateTimePeriodIso8601Serializer: KSerializer<DateTimePeriod> {
8282

8383
override val descriptor: SerialDescriptor =
84-
PrimitiveSerialDescriptor("DateTimePeriod", PrimitiveKind.STRING)
84+
PrimitiveSerialDescriptor("kotlinx.datetime.DateTimePeriod", PrimitiveKind.STRING)
8585

8686
override fun deserialize(decoder: Decoder): DateTimePeriod =
8787
DateTimePeriod.parse(decoder.decodeString())
@@ -110,7 +110,7 @@ public object DatePeriodComponentSerializer: KSerializer<DatePeriod> {
110110
private fun unexpectedNonzero(fieldName: String, value: Int) = unexpectedNonzero(fieldName, value.toLong())
111111

112112
override val descriptor: SerialDescriptor =
113-
buildClassSerialDescriptor("DatePeriod") {
113+
buildClassSerialDescriptor("kotlinx.datetime.DatePeriod") {
114114
element<Int>("years", isOptional = true)
115115
element<Int>("months", isOptional = true)
116116
element<Int>("days", isOptional = true)
@@ -166,7 +166,7 @@ public object DatePeriodComponentSerializer: KSerializer<DatePeriod> {
166166
public object DatePeriodIso8601Serializer: KSerializer<DatePeriod> {
167167

168168
override val descriptor: SerialDescriptor =
169-
PrimitiveSerialDescriptor("DatePeriod", PrimitiveKind.STRING)
169+
PrimitiveSerialDescriptor("kotlinx.datetime.DatePeriod", PrimitiveKind.STRING)
170170

171171
override fun deserialize(decoder: Decoder): DatePeriod =
172172
when (val period = DateTimePeriod.parse(decoder.decodeString())) {
@@ -178,4 +178,4 @@ public object DatePeriodIso8601Serializer: KSerializer<DatePeriod> {
178178
encoder.encodeString(value.toString())
179179
}
180180

181-
}
181+
}

core/common/src/serializers/DateTimeUnitSerializers.kt

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2021 JetBrains s.r.o.
2+
* Copyright 2019-2023 JetBrains s.r.o.
33
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
44
*/
55

@@ -23,7 +23,7 @@ public object TimeBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.TimeBase
2323

2424
// https://youtrack.jetbrains.com/issue/KT-63939
2525
override val descriptor: SerialDescriptor by lazy(LazyThreadSafetyMode.PUBLICATION) {
26-
buildClassSerialDescriptor("TimeBased") {
26+
buildClassSerialDescriptor("kotlinx.datetime.TimeBased") {
2727
element<Long>("nanoseconds")
2828
}
2929
}
@@ -35,7 +35,6 @@ public object TimeBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.TimeBase
3535
}
3636

3737
@OptIn(ExperimentalSerializationApi::class)
38-
@Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException`
3938
override fun deserialize(decoder: Decoder): DateTimeUnit.TimeBased {
4039
var seen = false
4140
var nanoseconds = 0L
@@ -51,12 +50,12 @@ public object TimeBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.TimeBase
5150
seen = true
5251
}
5352
CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262
54-
else -> throw UnknownFieldException(elementIndex)
53+
else -> throwUnknownIndexException(elementIndex)
5554
}
5655
}
5756
}
5857
}
59-
if (!seen) throw MissingFieldException("nanoseconds")
58+
if (!seen) throw MissingFieldException(missingField = "nanoseconds", serialName = descriptor.serialName)
6059
return DateTimeUnit.TimeBased(nanoseconds)
6160
}
6261
}
@@ -70,7 +69,7 @@ public object DayBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.DayBased>
7069

7170
// https://youtrack.jetbrains.com/issue/KT-63939
7271
override val descriptor: SerialDescriptor by lazy(LazyThreadSafetyMode.PUBLICATION) {
73-
buildClassSerialDescriptor("DayBased") {
72+
buildClassSerialDescriptor("kotlinx.datetime.DayBased") {
7473
element<Int>("days")
7574
}
7675
}
@@ -82,7 +81,6 @@ public object DayBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.DayBased>
8281
}
8382

8483
@OptIn(ExperimentalSerializationApi::class)
85-
@Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException`
8684
override fun deserialize(decoder: Decoder): DateTimeUnit.DayBased {
8785
var seen = false
8886
var days = 0
@@ -98,12 +96,12 @@ public object DayBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.DayBased>
9896
seen = true
9997
}
10098
CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262
101-
else -> throw UnknownFieldException(elementIndex)
99+
else -> throwUnknownIndexException(elementIndex)
102100
}
103101
}
104102
}
105103
}
106-
if (!seen) throw MissingFieldException("days")
104+
if (!seen) throw MissingFieldException(missingField = "days", serialName = descriptor.serialName)
107105
return DateTimeUnit.DayBased(days)
108106
}
109107
}
@@ -117,7 +115,7 @@ public object MonthBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.MonthBa
117115

118116
// https://youtrack.jetbrains.com/issue/KT-63939
119117
override val descriptor: SerialDescriptor by lazy(LazyThreadSafetyMode.PUBLICATION) {
120-
buildClassSerialDescriptor("MonthBased") {
118+
buildClassSerialDescriptor("kotlinx.datetime.MonthBased") {
121119
element<Int>("months")
122120
}
123121
}
@@ -129,7 +127,6 @@ public object MonthBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.MonthBa
129127
}
130128

131129
@OptIn(ExperimentalSerializationApi::class)
132-
@Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException`
133130
override fun deserialize(decoder: Decoder): DateTimeUnit.MonthBased {
134131
var seen = false
135132
var months = 0
@@ -145,12 +142,12 @@ public object MonthBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.MonthBa
145142
seen = true
146143
}
147144
CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262
148-
else -> throw UnknownFieldException(elementIndex)
145+
else -> throwUnknownIndexException(elementIndex)
149146
}
150147
}
151148
}
152149
}
153-
if (!seen) throw MissingFieldException("months")
150+
if (!seen) throw MissingFieldException(missingField = "months", serialName = descriptor.serialName)
154151
return DateTimeUnit.MonthBased(months)
155152
}
156153
}
@@ -160,7 +157,7 @@ public object MonthBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.MonthBa
160157
*
161158
* JSON example: `{"type":"DayBased","days":15}`
162159
*/
163-
@Suppress("EXPERIMENTAL_API_USAGE_ERROR", "INVISIBLE_MEMBER")
160+
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // https://github.com/Kotlin/kotlinx.serialization/issues/2460
164161
@OptIn(InternalSerializationApi::class)
165162
public object DateBasedDateTimeUnitSerializer: AbstractPolymorphicSerializer<DateTimeUnit.DateBased>() {
166163

@@ -197,9 +194,9 @@ public object DateBasedDateTimeUnitSerializer: AbstractPolymorphicSerializer<Dat
197194
*
198195
* JSON example: `{"type":"MonthBased","days":15}`
199196
*/
200-
@Suppress("EXPERIMENTAL_API_USAGE_ERROR", "INVISIBLE_MEMBER")
197+
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // https://github.com/Kotlin/kotlinx.serialization/issues/2460
201198
@OptIn(InternalSerializationApi::class)
202-
public object DateTimeUnitSerializer: AbstractPolymorphicSerializer<DateTimeUnit>() {
199+
public object DateTimeUnitSerializer : AbstractPolymorphicSerializer<DateTimeUnit>() {
203200

204201
// https://youtrack.jetbrains.com/issue/KT-63939
205202
private val impl by lazy(LazyThreadSafetyMode.PUBLICATION) {
@@ -224,4 +221,8 @@ public object DateTimeUnitSerializer: AbstractPolymorphicSerializer<DateTimeUnit
224221
override val descriptor: SerialDescriptor
225222
get() = impl.descriptor
226223

227-
}
224+
}
225+
226+
internal fun throwUnknownIndexException(index: Int): Nothing {
227+
throw SerializationException("An unknown field for index $index")
228+
}
Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright 2019-2021 JetBrains s.r.o.
2+
* Copyright 2019-2023 JetBrains s.r.o.
33
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
44
*/
55

66
package kotlinx.datetime.serializers
77

8-
import kotlinx.datetime.DayOfWeek
8+
import kotlinx.datetime.*
99
import kotlinx.serialization.*
1010
import kotlinx.serialization.descriptors.*
1111
import kotlinx.serialization.encoding.*
@@ -16,14 +16,8 @@ import kotlinx.serialization.internal.*
1616
*
1717
* JSON example: `"MONDAY"`
1818
*/
19-
@Suppress("INVISIBLE_MEMBER")
20-
public object DayOfWeekSerializer: KSerializer<DayOfWeek> {
21-
private val impl = EnumSerializer("Month", DayOfWeek.values())
22-
23-
override val descriptor: SerialDescriptor
24-
get() = impl.descriptor
25-
26-
override fun deserialize(decoder: Decoder): DayOfWeek = impl.deserialize(decoder)
27-
28-
override fun serialize(encoder: Encoder, value: DayOfWeek): Unit = impl.serialize(encoder, value)
29-
}
19+
@Suppress("EnumValuesSoftDeprecate") // createEnumSerializer requires an array
20+
public object DayOfWeekSerializer : KSerializer<DayOfWeek> by createEnumSerializer<DayOfWeek>(
21+
"kotlinx.datetime.DayOfWeek",
22+
DayOfWeek.values()
23+
)

core/common/src/serializers/InstantSerializers.kt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2021 JetBrains s.r.o.
2+
* Copyright 2019-2023 JetBrains s.r.o.
33
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
44
*/
55

@@ -18,10 +18,10 @@ import kotlinx.serialization.encoding.*
1818
* @see Instant.toString
1919
* @see Instant.parse
2020
*/
21-
public object InstantIso8601Serializer: KSerializer<Instant> {
21+
public object InstantIso8601Serializer : KSerializer<Instant> {
2222

2323
override val descriptor: SerialDescriptor =
24-
PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
24+
PrimitiveSerialDescriptor("kotlinx.datetime.Instant", PrimitiveKind.STRING)
2525

2626
override fun deserialize(decoder: Decoder): Instant =
2727
Instant.parse(decoder.decodeString())
@@ -37,29 +37,31 @@ public object InstantIso8601Serializer: KSerializer<Instant> {
3737
*
3838
* JSON example: `{"epochSeconds":1607505416,"nanosecondsOfSecond":124000}`
3939
*/
40-
public object InstantComponentSerializer: KSerializer<Instant> {
40+
public object InstantComponentSerializer : KSerializer<Instant> {
4141

4242
override val descriptor: SerialDescriptor =
43-
buildClassSerialDescriptor("Instant") {
43+
buildClassSerialDescriptor("kotlinx.datetime.Instant") {
4444
element<Long>("epochSeconds")
4545
element<Long>("nanosecondsOfSecond", isOptional = true)
4646
}
4747

4848
@OptIn(ExperimentalSerializationApi::class)
49-
@Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException`
5049
override fun deserialize(decoder: Decoder): Instant =
5150
decoder.decodeStructure(descriptor) {
5251
var epochSeconds: Long? = null
5352
var nanosecondsOfSecond = 0
54-
loop@while (true) {
53+
loop@ while (true) {
5554
when (val index = decodeElementIndex(descriptor)) {
5655
0 -> epochSeconds = decodeLongElement(descriptor, 0)
5756
1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1)
5857
CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262
5958
else -> throw SerializationException("Unexpected index: $index")
6059
}
6160
}
62-
if (epochSeconds == null) throw MissingFieldException("epochSeconds")
61+
if (epochSeconds == null) throw MissingFieldException(
62+
missingField = "epochSeconds",
63+
serialName = descriptor.serialName
64+
)
6365
Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond)
6466
}
6567

core/common/src/serializers/LocalDateSerializers.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2021 JetBrains s.r.o.
2+
* Copyright 2019-2023 JetBrains s.r.o.
33
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
44
*/
55

@@ -21,7 +21,7 @@ import kotlinx.serialization.encoding.*
2121
public object LocalDateIso8601Serializer: KSerializer<LocalDate> {
2222

2323
override val descriptor: SerialDescriptor =
24-
PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
24+
PrimitiveSerialDescriptor("kotlinx.datetime.LocalDate", PrimitiveKind.STRING)
2525

2626
override fun deserialize(decoder: Decoder): LocalDate =
2727
LocalDate.parse(decoder.decodeString())
@@ -40,14 +40,13 @@ public object LocalDateIso8601Serializer: KSerializer<LocalDate> {
4040
public object LocalDateComponentSerializer: KSerializer<LocalDate> {
4141

4242
override val descriptor: SerialDescriptor =
43-
buildClassSerialDescriptor("LocalDate") {
43+
buildClassSerialDescriptor("kotlinx.datetime.LocalDate") {
4444
element<Int>("year")
4545
element<Short>("month")
4646
element<Short>("day")
4747
}
4848

4949
@OptIn(ExperimentalSerializationApi::class)
50-
@Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException`
5150
override fun deserialize(decoder: Decoder): LocalDate =
5251
decoder.decodeStructure(descriptor) {
5352
var year: Int? = null
@@ -59,12 +58,12 @@ public object LocalDateComponentSerializer: KSerializer<LocalDate> {
5958
1 -> month = decodeShortElement(descriptor, 1)
6059
2 -> day = decodeShortElement(descriptor, 2)
6160
CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262
62-
else -> throw SerializationException("Unexpected index: $index")
61+
else -> throwUnknownIndexException(index)
6362
}
6463
}
65-
if (year == null) throw MissingFieldException("year")
66-
if (month == null) throw MissingFieldException("month")
67-
if (day == null) throw MissingFieldException("day")
64+
if (year == null) throw MissingFieldException(missingField = "year", serialName = descriptor.serialName)
65+
if (month == null) throw MissingFieldException(missingField = "month", serialName = descriptor.serialName)
66+
if (day == null) throw MissingFieldException(missingField = "day", serialName = descriptor.serialName)
6867
LocalDate(year, month.toInt(), day.toInt())
6968
}
7069

core/common/src/serializers/LocalDateTimeSerializers.kt

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2021 JetBrains s.r.o.
2+
* Copyright 2019-2023 JetBrains s.r.o.
33
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
44
*/
55

@@ -21,7 +21,7 @@ import kotlinx.serialization.encoding.*
2121
public object LocalDateTimeIso8601Serializer: KSerializer<LocalDateTime> {
2222

2323
override val descriptor: SerialDescriptor =
24-
PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
24+
PrimitiveSerialDescriptor("kotlinx.datetime.LocalDateTime", PrimitiveKind.STRING)
2525

2626
override fun deserialize(decoder: Decoder): LocalDateTime =
2727
LocalDateTime.parse(decoder.decodeString())
@@ -40,7 +40,7 @@ public object LocalDateTimeIso8601Serializer: KSerializer<LocalDateTime> {
4040
public object LocalDateTimeComponentSerializer: KSerializer<LocalDateTime> {
4141

4242
override val descriptor: SerialDescriptor =
43-
buildClassSerialDescriptor("LocalDateTime") {
43+
buildClassSerialDescriptor("kotlinx.datetime.LocalDateTime") {
4444
element<Int>("year")
4545
element<Short>("month")
4646
element<Short>("day")
@@ -51,7 +51,6 @@ public object LocalDateTimeComponentSerializer: KSerializer<LocalDateTime> {
5151
}
5252

5353
@OptIn(ExperimentalSerializationApi::class)
54-
@Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException`
5554
override fun deserialize(decoder: Decoder): LocalDateTime =
5655
decoder.decodeStructure(descriptor) {
5756
var year: Int? = null
@@ -74,11 +73,11 @@ public object LocalDateTimeComponentSerializer: KSerializer<LocalDateTime> {
7473
else -> throw SerializationException("Unexpected index: $index")
7574
}
7675
}
77-
if (year == null) throw MissingFieldException("year")
78-
if (month == null) throw MissingFieldException("month")
79-
if (day == null) throw MissingFieldException("day")
80-
if (hour == null) throw MissingFieldException("hour")
81-
if (minute == null) throw MissingFieldException("minute")
76+
if (year == null) throw MissingFieldException(missingField = "year", serialName = descriptor.serialName)
77+
if (month == null) throw MissingFieldException(missingField = "month", serialName = descriptor.serialName)
78+
if (day == null) throw MissingFieldException(missingField = "day", serialName = descriptor.serialName)
79+
if (hour == null) throw MissingFieldException(missingField = "hour", serialName = descriptor.serialName)
80+
if (minute == null) throw MissingFieldException(missingField = "minute", serialName = descriptor.serialName)
8281
LocalDateTime(year, month.toInt(), day.toInt(), hour.toInt(), minute.toInt(), second.toInt(), nanosecond)
8382
}
8483

0 commit comments

Comments
 (0)