Skip to content

Commit

Permalink
Add serializer for kotlin.uuid.Uuid (#2744)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sandwwraith authored Aug 26, 2024
1 parent 4646740 commit 62aa4bb
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ kotlin {
progressiveMode = true

optIn("kotlin.ExperimentalMultiplatform")
optIn("kotlin.ExperimentalStdlibApi")
optIn("kotlinx.serialization.InternalSerializationApi")
}
}
Expand Down
10 changes: 10 additions & 0 deletions core/api/kotlinx-serialization-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ public final class kotlinx/serialization/builtins/BuiltinSerializersKt {
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 static final fun serializer (Lkotlin/uuid/Uuid$Companion;)Lkotlinx/serialization/KSerializer;
}

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

public final class kotlinx/serialization/internal/UuidSerializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lkotlinx/serialization/internal/UuidSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lkotlin/uuid/Uuid;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlin/uuid/Uuid;)V
}

public final class kotlinx/serialization/modules/PolymorphicModuleBuilder {
public fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
public synthetic fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down
9 changes: 9 additions & 0 deletions core/api/kotlinx-serialization-core.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,14 @@ final object kotlinx.serialization.internal/UnitSerializer : kotlinx.serializati
final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin/Unit) // kotlinx.serialization.internal/UnitSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.Unit){}[0]
}

final object kotlinx.serialization.internal/UuidSerializer : kotlinx.serialization/KSerializer<kotlin.uuid/Uuid> { // kotlinx.serialization.internal/UuidSerializer|null[0]
final val descriptor // kotlinx.serialization.internal/UuidSerializer.descriptor|{}descriptor[0]
final fun <get-descriptor>(): kotlinx.serialization.descriptors/SerialDescriptor // kotlinx.serialization.internal/UuidSerializer.descriptor.<get-descriptor>|<get-descriptor>(){}[0]

final fun deserialize(kotlinx.serialization.encoding/Decoder): kotlin.uuid/Uuid // kotlinx.serialization.internal/UuidSerializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0]
final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin.uuid/Uuid) // kotlinx.serialization.internal/UuidSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.uuid.Uuid){}[0]
}

final val kotlinx.serialization.builtins/nullable // kotlinx.serialization.builtins/nullable|@kotlinx.serialization.KSerializer<0:0>{0§<kotlin.Any>}nullable[0]
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]
final val kotlinx.serialization.descriptors/capturedKClass // kotlinx.serialization.descriptors/capturedKClass|@kotlinx.serialization.descriptors.SerialDescriptor{}capturedKClass[0]
Expand All @@ -1062,6 +1070,7 @@ final val kotlinx.serialization.modules/EmptySerializersModule // kotlinx.serial
final fun <get-EmptySerializersModule>(): kotlinx.serialization.modules/SerializersModule // kotlinx.serialization.modules/EmptySerializersModule.<get-EmptySerializersModule>|<get-EmptySerializersModule>(){}[0]

final fun (kotlin.time/Duration.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin.time/Duration> // kotlinx.serialization.builtins/serializer|[email protected](){}[0]
final fun (kotlin.uuid/Uuid.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin.uuid/Uuid> // kotlinx.serialization.builtins/serializer|[email protected](){}[0]
final fun (kotlin/Boolean.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin/Boolean> // kotlinx.serialization.builtins/serializer|[email protected](){}[0]
final fun (kotlin/Byte.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin/Byte> // kotlinx.serialization.builtins/serializer|[email protected](){}[0]
final fun (kotlin/Char.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin/Char> // kotlinx.serialization.builtins/serializer|[email protected](){}[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.serialization.internal.*
import kotlin.reflect.*
import kotlinx.serialization.descriptors.*
import kotlin.time.Duration
import kotlin.uuid.*

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

/**
* Returns serializer for [Uuid].
* Serializer operates with a standard UUID string representation, also known as "hex-and-dash" format —
* [RFC 9562 section 4](https://www.rfc-editor.org/rfc/rfc9562.html#section-4).
*
* Serialization always produces lowercase string, deserialization is case-insensitive.
* More details can be found in the documentation of [Uuid.toString] and [Uuid.parse] functions.
*
* @see Uuid.toString
* @see Uuid.parse
*/
@ExperimentalUuidApi
public fun Uuid.Companion.serializer(): KSerializer<Uuid> = UuidSerializer

/**
* Returns serializer for [Nothing].
* Throws an exception when trying to encode or decode.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlin.time.Duration
import kotlin.uuid.*


@PublishedApi
Expand Down Expand Up @@ -37,3 +38,17 @@ internal object NothingSerializer : KSerializer<Nothing> {
throw SerializationException("'kotlin.Nothing' does not have instances")
}
}

@PublishedApi
@ExperimentalUuidApi
internal object UuidSerializer: KSerializer<Uuid> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlin.uuid.Uuid", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: Uuid) {
encoder.encodeString(value.toString())
}

override fun deserialize(decoder: Decoder): Uuid {
return Uuid.parse(decoder.decodeString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlin.native.concurrent.*
import kotlin.reflect.*
import kotlin.time.Duration
import kotlin.uuid.*

@OptIn(ExperimentalUnsignedTypes::class)
@OptIn(ExperimentalUnsignedTypes::class, ExperimentalUuidApi::class)
private val BUILTIN_SERIALIZERS = mapOf(
String::class to String.serializer(),
Char::class to Char.serializer(),
Expand Down Expand Up @@ -44,7 +44,8 @@ private val BUILTIN_SERIALIZERS = mapOf(
BooleanArray::class to BooleanArraySerializer(),
Unit::class to Unit.serializer(),
Nothing::class to NothingSerializer(),
Duration::class to Duration.serializer()
Duration::class to Duration.serializer(),
Uuid::class to Uuid.serializer()
)

internal class PrimitiveSerialDescriptor(
Expand Down
1 change: 1 addition & 0 deletions formats/json-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ kotlin {
sourceSets {
configureEach {
languageSettings {
optIn("kotlin.uuid.ExperimentalUuidApi")
optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
optIn("kotlinx.serialization.json.internal.JsonFriendModuleApi")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import kotlinx.serialization.test.*
import kotlin.reflect.*
import kotlin.test.*
import kotlin.time.Duration
import kotlin.uuid.*

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

@Test
@OptIn(ExperimentalUuidApi::class)
fun testLookupUuid() {
assertSame<KSerializer<*>?>(Uuid.serializer(), serializerOrNull(typeOf<Uuid>()))
// TODO: uncomment in 2.1 release
// assertSame<KSerializer<*>?>(Uuid.serializer(), serializer<Uuid>())
}

@Test
fun testCustomGeneric() {
val intBox = Box(42)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.features

import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlin.test.*
import kotlin.uuid.*

class UuidTest : JsonTestBase() {
@Test
fun testPlainUuid() {
val uuid = Uuid.random()
assertJsonFormAndRestored(Uuid.serializer(), uuid, "\"$uuid\"")
}

// TODO: write a test without @Contextual after 2.1.0 release
@Serializable
data class Holder(@Contextual val uuid: Uuid)

val json = Json { serializersModule = serializersModuleOf(Uuid.serializer()) }

@Test
fun testNested() {
val fixed = Uuid.parse("bc501c76-d806-4578-b45e-97a264e280f1")
assertJsonFormAndRestored(
Holder.serializer(),
Holder(fixed),
"""{"uuid":"bc501c76-d806-4578-b45e-97a264e280f1"}""",
json
)
}
}

0 comments on commit 62aa4bb

Please sign in to comment.