Skip to content

Commit ba67eb9

Browse files
committed
Rework the YearMonth serializers
1 parent 7104adf commit ba67eb9

File tree

7 files changed

+114
-24
lines changed

7 files changed

+114
-24
lines changed

core/api/kotlinx-datetime.api

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,15 @@ public abstract class kotlinx/datetime/serializers/FormattedUtcOffsetSerializer
11001100
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlinx/datetime/UtcOffset;)V
11011101
}
11021102

1103+
public abstract class kotlinx/datetime/serializers/FormattedYearMonthSerializer : kotlinx/serialization/KSerializer {
1104+
public fun <init> (Ljava/lang/String;Lkotlinx/datetime/format/DateTimeFormat;)V
1105+
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
1106+
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lkotlinx/datetime/YearMonth;
1107+
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
1108+
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
1109+
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlinx/datetime/YearMonth;)V
1110+
}
1111+
11031112
public final class kotlinx/datetime/serializers/InstantComponentSerializer : kotlinx/serialization/KSerializer {
11041113
public static final field INSTANCE Lkotlinx/datetime/serializers/InstantComponentSerializer;
11051114
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
@@ -1280,3 +1289,12 @@ public final class kotlinx/datetime/serializers/YearMonthIso8601Serializer : kot
12801289
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlinx/datetime/YearMonth;)V
12811290
}
12821291

1292+
public final class kotlinx/datetime/serializers/YearMonthSerializer : kotlinx/serialization/KSerializer {
1293+
public static final field INSTANCE Lkotlinx/datetime/serializers/YearMonthSerializer;
1294+
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
1295+
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lkotlinx/datetime/YearMonth;
1296+
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
1297+
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
1298+
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlinx/datetime/YearMonth;)V
1299+
}
1300+

core/api/kotlinx-datetime.klib.api

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,16 @@ abstract class kotlinx.datetime.serializers/FormattedUtcOffsetSerializer : kotli
190190
open fun serialize(kotlinx.serialization.encoding/Encoder, kotlinx.datetime/UtcOffset) // kotlinx.datetime.serializers/FormattedUtcOffsetSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlinx.datetime.UtcOffset){}[0]
191191
}
192192

193+
abstract class kotlinx.datetime.serializers/FormattedYearMonthSerializer : kotlinx.serialization/KSerializer<kotlinx.datetime/YearMonth> { // kotlinx.datetime.serializers/FormattedYearMonthSerializer|null[0]
194+
constructor <init>(kotlin/String, kotlinx.datetime.format/DateTimeFormat<kotlinx.datetime/YearMonth>) // kotlinx.datetime.serializers/FormattedYearMonthSerializer.<init>|<init>(kotlin.String;kotlinx.datetime.format.DateTimeFormat<kotlinx.datetime.YearMonth>){}[0]
195+
196+
open val descriptor // kotlinx.datetime.serializers/FormattedYearMonthSerializer.descriptor|{}descriptor[0]
197+
open fun <get-descriptor>(): kotlinx.serialization.descriptors/SerialDescriptor // kotlinx.datetime.serializers/FormattedYearMonthSerializer.descriptor.<get-descriptor>|<get-descriptor>(){}[0]
198+
199+
open fun deserialize(kotlinx.serialization.encoding/Decoder): kotlinx.datetime/YearMonth // kotlinx.datetime.serializers/FormattedYearMonthSerializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0]
200+
open fun serialize(kotlinx.serialization.encoding/Encoder, kotlinx.datetime/YearMonth) // kotlinx.datetime.serializers/FormattedYearMonthSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlinx.datetime.YearMonth){}[0]
201+
}
202+
193203
final class kotlinx.datetime.format/DateTimeComponents { // kotlinx.datetime.format/DateTimeComponents|null[0]
194204
final var amPm // kotlinx.datetime.format/DateTimeComponents.amPm|{}amPm[0]
195205
final fun <get-amPm>(): kotlinx.datetime.format/AmPmMarker? // kotlinx.datetime.format/DateTimeComponents.amPm.<get-amPm>|<get-amPm>(){}[0]
@@ -1053,6 +1063,14 @@ final object kotlinx.datetime.serializers/YearMonthIso8601Serializer : kotlinx.s
10531063
final fun serialize(kotlinx.serialization.encoding/Encoder, kotlinx.datetime/YearMonth) // kotlinx.datetime.serializers/YearMonthIso8601Serializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlinx.datetime.YearMonth){}[0]
10541064
}
10551065

1066+
final object kotlinx.datetime.serializers/YearMonthSerializer : kotlinx.serialization/KSerializer<kotlinx.datetime/YearMonth> { // kotlinx.datetime.serializers/YearMonthSerializer|null[0]
1067+
final val descriptor // kotlinx.datetime.serializers/YearMonthSerializer.descriptor|{}descriptor[0]
1068+
final fun <get-descriptor>(): kotlinx.serialization.descriptors/SerialDescriptor // kotlinx.datetime.serializers/YearMonthSerializer.descriptor.<get-descriptor>|<get-descriptor>(){}[0]
1069+
1070+
final fun deserialize(kotlinx.serialization.encoding/Decoder): kotlinx.datetime/YearMonth // kotlinx.datetime.serializers/YearMonthSerializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0]
1071+
final fun serialize(kotlinx.serialization.encoding/Encoder, kotlinx.datetime/YearMonth) // kotlinx.datetime.serializers/YearMonthSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlinx.datetime.YearMonth){}[0]
1072+
}
1073+
10561074
final val kotlinx.datetime/isDistantFuture // kotlinx.datetime/isDistantFuture|@kotlinx.datetime.Instant{}isDistantFuture[0]
10571075
final fun (kotlinx.datetime/Instant).<get-isDistantFuture>(): kotlin/Boolean // kotlinx.datetime/isDistantFuture.<get-isDistantFuture>|<get-isDistantFuture>@kotlinx.datetime.Instant(){}[0]
10581076
final val kotlinx.datetime/isDistantPast // kotlinx.datetime/isDistantPast|@kotlinx.datetime.Instant{}isDistantPast[0]

core/common/src/YearMonth.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ package kotlinx.datetime
77

88
import kotlinx.datetime.format.*
99
import kotlinx.datetime.internal.*
10-
import kotlinx.datetime.serializers.YearMonthIso8601Serializer
10+
import kotlinx.datetime.serializers.*
1111
import kotlinx.serialization.Serializable
1212

1313
/**
@@ -57,7 +57,7 @@ import kotlinx.serialization.Serializable
5757
* @sample kotlinx.datetime.test.samples.YearMonthSamples.simpleParsingAndFormatting
5858
* @sample kotlinx.datetime.test.samples.YearMonthSamples.customFormat
5959
*/
60-
@Serializable(with = YearMonthIso8601Serializer::class)
60+
@Serializable(with = YearMonthSerializer::class)
6161
public expect class YearMonth
6262
/**
6363
* Constructs a [YearMonth] instance from the given year-month components.

core/common/src/serializers/YearMonthSerializers.kt

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package kotlinx.datetime.serializers
77

88
import kotlinx.datetime.YearMonth
9+
import kotlinx.datetime.format.DateTimeFormat
910
import kotlinx.datetime.number
1011
import kotlinx.serialization.*
1112
import kotlinx.serialization.descriptors.*
@@ -16,22 +17,10 @@ import kotlinx.serialization.encoding.*
1617
*
1718
* JSON example: `"2020-01"`
1819
*
19-
* @see YearMonth.parse
20-
* @see YearMonth.toString
20+
* @see YearMonth.Formats.ISO
2121
*/
22-
public object YearMonthIso8601Serializer: KSerializer<YearMonth> {
23-
24-
override val descriptor: SerialDescriptor =
25-
PrimitiveSerialDescriptor("kotlinx.datetime.YearMonth", PrimitiveKind.STRING)
26-
27-
override fun deserialize(decoder: Decoder): YearMonth =
28-
YearMonth.parse(decoder.decodeString())
29-
30-
override fun serialize(encoder: Encoder, value: YearMonth) {
31-
encoder.encodeString(value.toString())
32-
}
33-
34-
}
22+
public object YearMonthIso8601Serializer : KSerializer<YearMonth>
23+
by YearMonth.Formats.ISO.asKSerializer("kotlinx.datetime.YearMonth/ISO")
3524

3625
/**
3726
* A serializer for [YearMonth] that represents a value as its components.
@@ -41,7 +30,7 @@ public object YearMonthIso8601Serializer: KSerializer<YearMonth> {
4130
public object YearMonthComponentSerializer: KSerializer<YearMonth> {
4231

4332
override val descriptor: SerialDescriptor =
44-
buildClassSerialDescriptor("kotlinx.datetime.LocalDate") {
33+
buildClassSerialDescriptor("kotlinx.datetime.YearMonth/components") {
4534
element<Int>("year")
4635
element<Short>("month")
4736
}
@@ -51,11 +40,11 @@ public object YearMonthComponentSerializer: KSerializer<YearMonth> {
5140
decoder.decodeStructure(descriptor) {
5241
var year: Int? = null
5342
var month: Short? = null
54-
loop@while (true) {
43+
while (true) {
5544
when (val index = decodeElementIndex(descriptor)) {
5645
0 -> year = decodeIntElement(descriptor, 0)
5746
1 -> month = decodeShortElement(descriptor, 1)
58-
CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262
47+
CompositeDecoder.DECODE_DONE -> break
5948
else -> throwUnknownIndexException(index)
6049
}
6150
}
@@ -72,3 +61,49 @@ public object YearMonthComponentSerializer: KSerializer<YearMonth> {
7261
}
7362

7463
}
64+
65+
/**
66+
* An abstract serializer for [YearMonth] values that uses
67+
* a custom [DateTimeFormat] to serialize and deserialize the value.
68+
*
69+
* [name] is the name of the serializer.
70+
* The [SerialDescriptor.serialName] of the resulting serializer is `kotlinx.datetime.YearMonth/serializer/`[name].
71+
* [SerialDescriptor.serialName] must be unique across all serializers in the same serialization context.
72+
* When defining a serializer in a library, it is recommended to use the fully qualified class name in [name]
73+
* to avoid conflicts with serializers defined by other libraries and client code.
74+
*
75+
* This serializer is abstract and must be subclassed to provide a concrete serializer.
76+
* Example:
77+
* ```
78+
* // serializes YearMonth(2020, 1) as the string "202001"
79+
* object IsoBasicYearMonthSerializer :
80+
* FormattedYearMonthSerializer("my.package.ISO_BASIC", YearMonth.Format { year(); monthNumber() })
81+
* ```
82+
*
83+
* Note that [YearMonth] is [kotlinx.serialization.Serializable] by default,
84+
* so it is not necessary to create custom serializers when the format is not important.
85+
* Additionally, [YearMonthIso8601Serializer] is provided for the ISO 8601 format.
86+
*/
87+
public abstract class FormattedYearMonthSerializer(
88+
name: String, format: DateTimeFormat<YearMonth>
89+
) : KSerializer<YearMonth> by format.asKSerializer("kotlinx.datetime.YearMonth/serializer/$name")
90+
91+
/**
92+
* A serializer for [YearMonth] that uses the default [YearMonth.toString]/[YearMonth.parse].
93+
*
94+
* JSON example: `"2020-01"`
95+
*/
96+
@PublishedApi
97+
internal object YearMonthSerializer: KSerializer<YearMonth> {
98+
99+
override val descriptor: SerialDescriptor =
100+
PrimitiveSerialDescriptor("kotlinx.datetime.YearMonth", PrimitiveKind.STRING)
101+
102+
override fun deserialize(decoder: Decoder): YearMonth =
103+
YearMonth.parse(decoder.decodeString())
104+
105+
override fun serialize(encoder: Encoder, value: YearMonth) {
106+
encoder.encodeString(value.toString())
107+
}
108+
109+
}

core/commonKotlin/src/YearMonth.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ package kotlinx.datetime
77

88
import kotlinx.datetime.format.*
99
import kotlinx.datetime.internal.*
10-
import kotlinx.datetime.serializers.YearMonthIso8601Serializer
10+
import kotlinx.datetime.serializers.YearMonthSerializer
1111
import kotlinx.serialization.Serializable
1212

13-
@Serializable(with = YearMonthIso8601Serializer::class)
13+
@Serializable(with = YearMonthSerializer::class)
1414
public actual class YearMonth
1515
public actual constructor(public actual val year: Int, month: Int) : Comparable<YearMonth> {
1616
internal actual val monthNumber: Int = month

core/jvm/src/YearMonthJvm.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ package kotlinx.datetime
77

88
import kotlinx.datetime.format.*
99
import kotlinx.datetime.internal.*
10-
import kotlinx.datetime.serializers.YearMonthIso8601Serializer
10+
import kotlinx.datetime.serializers.YearMonthSerializer
1111
import kotlinx.serialization.Serializable
1212
import java.time.DateTimeException
1313
import java.time.format.DateTimeFormatterBuilder
1414
import java.time.format.DateTimeParseException
1515
import java.time.format.SignStyle
1616
import java.time.YearMonth as jtYearMonth
1717

18-
@Serializable(with = YearMonthIso8601Serializer::class)
18+
@Serializable(with = YearMonthSerializer::class)
1919
public actual class YearMonth internal constructor(
2020
internal val value: jtYearMonth
2121
) : Comparable<YearMonth>, java.io.Serializable {

integration-testing/serialization/common/test/YearMonthSerializationTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,37 @@ class YearMonthSerializationTest {
5353

5454
@Test
5555
fun testIso8601Serialization() {
56+
assertKSerializerName("kotlinx.datetime.YearMonth/ISO", YearMonthIso8601Serializer)
5657
iso8601Serialization(YearMonthIso8601Serializer)
5758
}
5859

5960
@Test
6061
fun testComponentSerialization() {
62+
assertKSerializerName("kotlinx.datetime.YearMonth/components", YearMonthComponentSerializer)
6163
componentSerialization(YearMonthComponentSerializer)
6264
}
6365

6466
@Test
6567
fun testDefaultSerializers() {
6668
// should be the same as the ISO 8601
69+
assertKSerializerName<YearMonth>("kotlinx.datetime.YearMonth", Json.serializersModule.serializer())
6770
iso8601Serialization(Json.serializersModule.serializer())
6871
}
6972

73+
object IsoBasicYearMonthSerializer : FormattedYearMonthSerializer("ISO_BASIC", YearMonth.Format {
74+
year(); monthNumber()
75+
})
76+
77+
@Test
78+
fun testCustomSerializer() {
79+
assertKSerializerName("kotlinx.datetime.YearMonth/serializer/ISO_BASIC", IsoBasicYearMonthSerializer)
80+
for ((yearMonth, json) in listOf(
81+
Pair(YearMonth(2020, 12), "\"202012\""),
82+
Pair(YearMonth(-2020, 1), "\"-202001\""),
83+
Pair(YearMonth(2019, 10), "\"201910\""),
84+
)) {
85+
assertEquals(json, Json.encodeToString(IsoBasicYearMonthSerializer, yearMonth))
86+
assertEquals(yearMonth, Json.decodeFromString(IsoBasicYearMonthSerializer, json))
87+
}
88+
}
7089
}

0 commit comments

Comments
 (0)