Skip to content

Commit 62aa4bb

Browse files
authored
Add serializer for kotlin.uuid.Uuid (#2744)
in a simple .toString()/.parse() form. It is expected to be added as a standard serializer to the Kotlin 2.1 serialization plugin. Fixes #2730
1 parent 4646740 commit 62aa4bb

File tree

9 files changed

+100
-4
lines changed

9 files changed

+100
-4
lines changed

buildSrc/src/main/kotlin/source-sets-conventions.gradle.kts

-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ kotlin {
6666
progressiveMode = true
6767

6868
optIn("kotlin.ExperimentalMultiplatform")
69-
optIn("kotlin.ExperimentalStdlibApi")
7069
optIn("kotlinx.serialization.InternalSerializationApi")
7170
}
7271
}

core/api/kotlinx-serialization-core.api

+10
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ public final class kotlinx/serialization/builtins/BuiltinSerializersKt {
199199
public static final fun serializer (Lkotlin/jvm/internal/ShortCompanionObject;)Lkotlinx/serialization/KSerializer;
200200
public static final fun serializer (Lkotlin/jvm/internal/StringCompanionObject;)Lkotlinx/serialization/KSerializer;
201201
public static final fun serializer (Lkotlin/time/Duration$Companion;)Lkotlinx/serialization/KSerializer;
202+
public static final fun serializer (Lkotlin/uuid/Uuid$Companion;)Lkotlinx/serialization/KSerializer;
202203
}
203204

204205
public final class kotlinx/serialization/builtins/LongAsStringSerializer : kotlinx/serialization/KSerializer {
@@ -1293,6 +1294,15 @@ public final class kotlinx/serialization/internal/UnitSerializer : kotlinx/seria
12931294
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlin/Unit;)V
12941295
}
12951296

1297+
public final class kotlinx/serialization/internal/UuidSerializer : kotlinx/serialization/KSerializer {
1298+
public static final field INSTANCE Lkotlinx/serialization/internal/UuidSerializer;
1299+
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
1300+
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lkotlin/uuid/Uuid;
1301+
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
1302+
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
1303+
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlin/uuid/Uuid;)V
1304+
}
1305+
12961306
public final class kotlinx/serialization/modules/PolymorphicModuleBuilder {
12971307
public fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
12981308
public synthetic fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V

core/api/kotlinx-serialization-core.klib.api

+9
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,14 @@ final object kotlinx.serialization.internal/UnitSerializer : kotlinx.serializati
10461046
final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin/Unit) // kotlinx.serialization.internal/UnitSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.Unit){}[0]
10471047
}
10481048

1049+
final object kotlinx.serialization.internal/UuidSerializer : kotlinx.serialization/KSerializer<kotlin.uuid/Uuid> { // kotlinx.serialization.internal/UuidSerializer|null[0]
1050+
final val descriptor // kotlinx.serialization.internal/UuidSerializer.descriptor|{}descriptor[0]
1051+
final fun <get-descriptor>(): kotlinx.serialization.descriptors/SerialDescriptor // kotlinx.serialization.internal/UuidSerializer.descriptor.<get-descriptor>|<get-descriptor>(){}[0]
1052+
1053+
final fun deserialize(kotlinx.serialization.encoding/Decoder): kotlin.uuid/Uuid // kotlinx.serialization.internal/UuidSerializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0]
1054+
final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin.uuid/Uuid) // kotlinx.serialization.internal/UuidSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.uuid.Uuid){}[0]
1055+
}
1056+
10491057
final val kotlinx.serialization.builtins/nullable // kotlinx.serialization.builtins/nullable|@kotlinx.serialization.KSerializer<0:0>{0§<kotlin.Any>}nullable[0]
10501058
final fun <#A1: kotlin/Any> (kotlinx.serialization/KSerializer<#A1>).<get-nullable>(): kotlinx.serialization/KSerializer<#A1?> // kotlinx.serialization.builtins/nullable.<get-nullable>|<get-nullable>@kotlinx.serialization.KSerializer<0:0>(){0§<kotlin.Any>}[0]
10511059
final val kotlinx.serialization.descriptors/capturedKClass // kotlinx.serialization.descriptors/capturedKClass|@kotlinx.serialization.descriptors.SerialDescriptor{}capturedKClass[0]
@@ -1062,6 +1070,7 @@ final val kotlinx.serialization.modules/EmptySerializersModule // kotlinx.serial
10621070
final fun <get-EmptySerializersModule>(): kotlinx.serialization.modules/SerializersModule // kotlinx.serialization.modules/EmptySerializersModule.<get-EmptySerializersModule>|<get-EmptySerializersModule>(){}[0]
10631071

10641072
final fun (kotlin.time/Duration.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin.time/Duration> // kotlinx.serialization.builtins/serializer|[email protected](){}[0]
1073+
final fun (kotlin.uuid/Uuid.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin.uuid/Uuid> // kotlinx.serialization.builtins/serializer|[email protected](){}[0]
10651074
final fun (kotlin/Boolean.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin/Boolean> // kotlinx.serialization.builtins/serializer|[email protected](){}[0]
10661075
final fun (kotlin/Byte.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin/Byte> // kotlinx.serialization.builtins/serializer|[email protected](){}[0]
10671076
final fun (kotlin/Char.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin/Char> // kotlinx.serialization.builtins/serializer|[email protected](){}[0]

core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt

+15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import kotlinx.serialization.internal.*
1010
import kotlin.reflect.*
1111
import kotlinx.serialization.descriptors.*
1212
import kotlin.time.Duration
13+
import kotlin.uuid.*
1314

1415
/**
1516
* Returns a nullable serializer for the given serializer of non-null type.
@@ -251,6 +252,20 @@ public fun UShort.Companion.serializer(): KSerializer<UShort> = UShortSerializer
251252
*/
252253
public fun Duration.Companion.serializer(): KSerializer<Duration> = DurationSerializer
253254

255+
/**
256+
* Returns serializer for [Uuid].
257+
* Serializer operates with a standard UUID string representation, also known as "hex-and-dash" format —
258+
* [RFC 9562 section 4](https://www.rfc-editor.org/rfc/rfc9562.html#section-4).
259+
*
260+
* Serialization always produces lowercase string, deserialization is case-insensitive.
261+
* More details can be found in the documentation of [Uuid.toString] and [Uuid.parse] functions.
262+
*
263+
* @see Uuid.toString
264+
* @see Uuid.parse
265+
*/
266+
@ExperimentalUuidApi
267+
public fun Uuid.Companion.serializer(): KSerializer<Uuid> = UuidSerializer
268+
254269
/**
255270
* Returns serializer for [Nothing].
256271
* Throws an exception when trying to encode or decode.

core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt

+15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor
1010
import kotlinx.serialization.encoding.Decoder
1111
import kotlinx.serialization.encoding.Encoder
1212
import kotlin.time.Duration
13+
import kotlin.uuid.*
1314

1415

1516
@PublishedApi
@@ -37,3 +38,17 @@ internal object NothingSerializer : KSerializer<Nothing> {
3738
throw SerializationException("'kotlin.Nothing' does not have instances")
3839
}
3940
}
41+
42+
@PublishedApi
43+
@ExperimentalUuidApi
44+
internal object UuidSerializer: KSerializer<Uuid> {
45+
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlin.uuid.Uuid", PrimitiveKind.STRING)
46+
47+
override fun serialize(encoder: Encoder, value: Uuid) {
48+
encoder.encodeString(value.toString())
49+
}
50+
51+
override fun deserialize(decoder: Decoder): Uuid {
52+
return Uuid.parse(decoder.decodeString())
53+
}
54+
}

core/commonMain/src/kotlinx/serialization/internal/Primitives.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import kotlinx.serialization.*
1111
import kotlinx.serialization.builtins.*
1212
import kotlinx.serialization.descriptors.*
1313
import kotlinx.serialization.encoding.*
14-
import kotlin.native.concurrent.*
1514
import kotlin.reflect.*
1615
import kotlin.time.Duration
16+
import kotlin.uuid.*
1717

18-
@OptIn(ExperimentalUnsignedTypes::class)
18+
@OptIn(ExperimentalUnsignedTypes::class, ExperimentalUuidApi::class)
1919
private val BUILTIN_SERIALIZERS = mapOf(
2020
String::class to String.serializer(),
2121
Char::class to Char.serializer(),
@@ -44,7 +44,8 @@ private val BUILTIN_SERIALIZERS = mapOf(
4444
BooleanArray::class to BooleanArraySerializer(),
4545
Unit::class to Unit.serializer(),
4646
Nothing::class to NothingSerializer(),
47-
Duration::class to Duration.serializer()
47+
Duration::class to Duration.serializer(),
48+
Uuid::class to Uuid.serializer()
4849
)
4950

5051
internal class PrimitiveSerialDescriptor(

formats/json-tests/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ kotlin {
2626
sourceSets {
2727
configureEach {
2828
languageSettings {
29+
optIn("kotlin.uuid.ExperimentalUuidApi")
2930
optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
3031
optIn("kotlinx.serialization.json.internal.JsonFriendModuleApi")
3132
}

formats/json-tests/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import kotlinx.serialization.test.*
1515
import kotlin.reflect.*
1616
import kotlin.test.*
1717
import kotlin.time.Duration
18+
import kotlin.uuid.*
1819

1920
@Suppress("RemoveExplicitTypeArguments") // This is exactly what's being tested
2021
class SerializersLookupTest : JsonTestBase() {
@@ -141,6 +142,14 @@ class SerializersLookupTest : JsonTestBase() {
141142
assertSame(Duration.serializer(), serializer<Duration>())
142143
}
143144

145+
@Test
146+
@OptIn(ExperimentalUuidApi::class)
147+
fun testLookupUuid() {
148+
assertSame<KSerializer<*>?>(Uuid.serializer(), serializerOrNull(typeOf<Uuid>()))
149+
// TODO: uncomment in 2.1 release
150+
// assertSame<KSerializer<*>?>(Uuid.serializer(), serializer<Uuid>())
151+
}
152+
144153
@Test
145154
fun testCustomGeneric() {
146155
val intBox = Box(42)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.serialization.features
6+
7+
import kotlinx.serialization.*
8+
import kotlinx.serialization.builtins.*
9+
import kotlinx.serialization.json.*
10+
import kotlinx.serialization.modules.*
11+
import kotlin.test.*
12+
import kotlin.uuid.*
13+
14+
class UuidTest : JsonTestBase() {
15+
@Test
16+
fun testPlainUuid() {
17+
val uuid = Uuid.random()
18+
assertJsonFormAndRestored(Uuid.serializer(), uuid, "\"$uuid\"")
19+
}
20+
21+
// TODO: write a test without @Contextual after 2.1.0 release
22+
@Serializable
23+
data class Holder(@Contextual val uuid: Uuid)
24+
25+
val json = Json { serializersModule = serializersModuleOf(Uuid.serializer()) }
26+
27+
@Test
28+
fun testNested() {
29+
val fixed = Uuid.parse("bc501c76-d806-4578-b45e-97a264e280f1")
30+
assertJsonFormAndRestored(
31+
Holder.serializer(),
32+
Holder(fixed),
33+
"""{"uuid":"bc501c76-d806-4578-b45e-97a264e280f1"}""",
34+
json
35+
)
36+
}
37+
}

0 commit comments

Comments
 (0)