From 1b0a238794c744b7908e51b335dad1b853d1c315 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Thu, 2 Jun 2022 22:32:40 +0300 Subject: [PATCH 1/3] Added support for the `kotlin.time.Duration` class as built-in --- core/api/kotlinx-serialization-core.api | 10 +++++++ .../builtins/BuiltinSerializers.kt | 7 +++++ .../internal/BuiltInSerializers.kt | 29 +++++++++++++++++++ .../serialization/internal/Primitives.kt | 4 ++- .../serialization/SerializersLookupTest.kt | 7 +++++ .../serialization/features/DurationTest.kt | 29 +++++++++++++++++++ 6 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt create mode 100644 formats/json/commonTest/src/kotlinx/serialization/features/DurationTest.kt diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index 2afcf24aa2..4aba1e3f39 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -175,6 +175,7 @@ public final class kotlinx/serialization/builtins/BuiltinSerializersKt { public static final fun serializer (Lkotlin/jvm/internal/LongCompanionObject;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/jvm/internal/ShortCompanionObject;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/jvm/internal/StringCompanionObject;)Lkotlinx/serialization/KSerializer; + public static final fun serializer (Lkotlin/time/Duration$Companion;)Lkotlinx/serialization/KSerializer; } public final class kotlinx/serialization/builtins/LongAsStringSerializer : kotlinx/serialization/KSerializer { @@ -662,6 +663,15 @@ public final class kotlinx/serialization/internal/DoubleSerializer : kotlinx/ser public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V } +public final class kotlinx/serialization/internal/DurationSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Lkotlinx/serialization/internal/DurationSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize-5sfh64U (Lkotlinx/serialization/encoding/Decoder;)J + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize-HG0u8IE (Lkotlinx/serialization/encoding/Encoder;J)V +} + public final class kotlinx/serialization/internal/ElementMarker { public fun (Lkotlinx/serialization/descriptors/SerialDescriptor;Lkotlin/jvm/functions/Function2;)V public final fun mark (I)V diff --git a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt index 147f25d9b0..3eeb47b9d2 100644 --- a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt +++ b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt @@ -9,6 +9,7 @@ import kotlinx.serialization.* import kotlinx.serialization.internal.* import kotlin.reflect.* import kotlinx.serialization.descriptors.* +import kotlin.time.Duration /** * Returns a nullable serializer for the given serializer of non-null type. @@ -217,3 +218,9 @@ public fun UByte.Companion.serializer(): KSerializer = UByteSerializer @ExperimentalSerializationApi @ExperimentalUnsignedTypes public fun UShort.Companion.serializer(): KSerializer = UShortSerializer + +/** + * Returns serializer for [Duration]. + */ +@ExperimentalSerializationApi +public fun Duration.Companion.serializer(): KSerializer = DurationSerializer diff --git a/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt b/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt new file mode 100644 index 0000000000..82f057aa9e --- /dev/null +++ b/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +@file:OptIn(ExperimentalSerializationApi::class) + +package kotlinx.serialization.internal + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.time.Duration + + +@PublishedApi +@ExperimentalSerializationApi +internal object DurationSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlin.time.Duration", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Duration) { + encoder.encodeString(value.toIsoString()) + } + + override fun deserialize(decoder: Decoder): Duration { + return Duration.parseIsoString(decoder.decodeString()) + } +} diff --git a/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt b/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt index ab127ffa4d..dfc9aafa71 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt @@ -13,6 +13,7 @@ import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* import kotlin.native.concurrent.* import kotlin.reflect.* +import kotlin.time.Duration @SharedImmutable private val BUILTIN_SERIALIZERS = mapOf( @@ -33,7 +34,8 @@ private val BUILTIN_SERIALIZERS = mapOf( ByteArray::class to ByteArraySerializer(), Boolean::class to Boolean.serializer(), BooleanArray::class to BooleanArraySerializer(), - Unit::class to Unit.serializer() + Unit::class to Unit.serializer(), + Duration::class to Duration.serializer() ) internal class PrimitiveSerialDescriptor( diff --git a/formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt b/formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt index 18a69404b5..5451263f72 100644 --- a/formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt +++ b/formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt @@ -14,6 +14,7 @@ import kotlinx.serialization.modules.* import kotlinx.serialization.test.* import kotlin.reflect.* import kotlin.test.* +import kotlin.time.Duration @Suppress("RemoveExplicitTypeArguments") // This is exactly what's being tested class SerializersLookupTest : JsonTestBase() { @@ -243,6 +244,12 @@ class SerializersLookupTest : JsonTestBase() { } } +// TODO uncomment when Kotlin 1.7.20 is released +// @Test +// fun testLookupDuration() = noLegacyJs { +// assertNotNull(serializerOrNull(typeOf())) +// } + private inline fun assertSerializedWithType( expected: String, value: T, diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/DurationTest.kt b/formats/json/commonTest/src/kotlinx/serialization/features/DurationTest.kt new file mode 100644 index 0000000000..bb1078bfde --- /dev/null +++ b/formats/json/commonTest/src/kotlinx/serialization/features/DurationTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.features + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonTestBase +import kotlin.test.Test +import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.toDuration + + +class DurationTest : JsonTestBase() { + + @Serializable + data class DurationHolder(val duration: Duration) + +// TODO uncomment when Kotlin 1.7.20 is released +// @Test +// fun testDuration() { +// assertJsonFormAndRestored( +// DurationHolder.serializer(), +// DurationHolder(1000.toDuration(DurationUnit.SECONDS)), +// """{"duration":"PT16M40S"}""" +// ) +// } +} From 2964cbea7c8358c6cda79fbeb92c5a60daecafeb Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Fri, 3 Jun 2022 18:41:48 +0300 Subject: [PATCH 2/3] review fixes --- .../kotlinx/serialization/builtins/BuiltinSerializers.kt | 2 +- .../kotlinx/serialization/internal/BuiltInSerializers.kt | 4 ---- .../src/kotlinx/serialization/features/DurationTest.kt | 6 ++---- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt index 3eeb47b9d2..6c7ac55a13 100644 --- a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt +++ b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt @@ -221,6 +221,6 @@ public fun UShort.Companion.serializer(): KSerializer = UShortSerializer /** * Returns serializer for [Duration]. + * By default, it is serialized as a string that represents a duration in ISO-8601 format. */ -@ExperimentalSerializationApi public fun Duration.Companion.serializer(): KSerializer = DurationSerializer diff --git a/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt b/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt index 82f057aa9e..13735ee7ff 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt @@ -1,11 +1,8 @@ /* * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:OptIn(ExperimentalSerializationApi::class) - package kotlinx.serialization.internal -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor @@ -15,7 +12,6 @@ import kotlin.time.Duration @PublishedApi -@ExperimentalSerializationApi internal object DurationSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlin.time.Duration", PrimitiveKind.STRING) diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/DurationTest.kt b/formats/json/commonTest/src/kotlinx/serialization/features/DurationTest.kt index bb1078bfde..a94ae43f07 100644 --- a/formats/json/commonTest/src/kotlinx/serialization/features/DurationTest.kt +++ b/formats/json/commonTest/src/kotlinx/serialization/features/DurationTest.kt @@ -13,11 +13,9 @@ import kotlin.time.toDuration class DurationTest : JsonTestBase() { - - @Serializable - data class DurationHolder(val duration: Duration) - // TODO uncomment when Kotlin 1.7.20 is released +// @Serializable +// data class DurationHolder(val duration: Duration) // @Test // fun testDuration() { // assertJsonFormAndRestored( From c94b609ab6d097ce0c634c8ec73231f30e0f9664 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Tue, 14 Jun 2022 15:02:12 +0300 Subject: [PATCH 3/3] review fixes --- .../builtins/BuiltinSerializers.kt | 4 +++- .../BasicTypesSerializationTest.kt | 23 ++++++++++++++++++- .../serialization/SerializersLookupTest.kt | 1 + 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt index 6c7ac55a13..ddc4278328 100644 --- a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt +++ b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt @@ -221,6 +221,8 @@ public fun UShort.Companion.serializer(): KSerializer = UShortSerializer /** * Returns serializer for [Duration]. - * By default, it is serialized as a string that represents a duration in ISO-8601 format. + * It is serialized as a string that represents a duration in the ISO-8601 format. + * + * The result of serialization is similar to calling [Duration.toIsoString], for deserialization is [Duration.parseIsoString]. */ public fun Duration.Companion.serializer(): KSerializer = DurationSerializer diff --git a/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt b/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt index 82cbb2aedc..dc443fd2f9 100644 --- a/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt +++ b/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt @@ -4,12 +4,13 @@ package kotlinx.serialization +import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME -import kotlinx.serialization.internal.* import kotlinx.serialization.modules.* import kotlin.test.* +import kotlin.time.Duration /* * Test ensures that type that aggregate all basic (primitive/collection/maps/arrays) @@ -170,4 +171,24 @@ class BasicTypesSerializationTest { assertEquals(umbrellaInstance, other) assertNotSame(umbrellaInstance, other) } + + @Test + fun testEncodeDuration() { + val sb = StringBuilder() + val out = KeyValueOutput(sb) + + val duration = Duration.parseIsoString("P4DT12H30M5S") + out.encodeSerializableValue(Duration.serializer(), duration) + + assertEquals("\"${duration.toIsoString()}\"", sb.toString()) + } + + @Test + fun testDecodeDuration() { + val durationString = "P4DT12H30M5S" + val inp = KeyValueInput(Parser(StringReader("\"$durationString\""))) + val other = inp.decodeSerializableValue(Duration.serializer()) + assertEquals(Duration.parseIsoString(durationString), other) + } + } diff --git a/formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt b/formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt index 5451263f72..c5dcf20166 100644 --- a/formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt +++ b/formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt @@ -248,6 +248,7 @@ class SerializersLookupTest : JsonTestBase() { // @Test // fun testLookupDuration() = noLegacyJs { // assertNotNull(serializerOrNull(typeOf())) +// assertSame(Duration.serializer(), serializer()) // } private inline fun assertSerializedWithType(