From 7496cee0b0a40c995d17a0c2623b35fed2ceeec5 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 30 Jan 2024 11:13:04 -0500 Subject: [PATCH] DOCSP-35884: custom serializer example --- examples/build.gradle.kts | 1 + .../test/kotlin/KotlinXSerializationTest.kt | 60 +++++++++++++++++++ ...ationTest.snippet.kserializer-dataclass.kt | 7 +++ ...nXSerializationTest.snippet.kserializer.kt | 17 ++++++ .../data-formats/serialization.txt | 22 +++++++ 5 files changed, 107 insertions(+) create mode 100644 source/examples/generated/KotlinXSerializationTest.snippet.kserializer-dataclass.kt create mode 100644 source/examples/generated/KotlinXSerializationTest.snippet.kserializer.kt diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 661631cf..424bc68a 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation("org.mongodb:bson-kotlinx:4.10.0-alpha1") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") } tasks.test { diff --git a/examples/src/test/kotlin/KotlinXSerializationTest.kt b/examples/src/test/kotlin/KotlinXSerializationTest.kt index 132adab4..bd08de64 100644 --- a/examples/src/test/kotlin/KotlinXSerializationTest.kt +++ b/examples/src/test/kotlin/KotlinXSerializationTest.kt @@ -1,4 +1,5 @@ +import com.mongodb.client.model.Filters.eq import com.mongodb.kotlin.client.coroutine.MongoClient import config.getConfig import kotlinx.coroutines.flow.first @@ -8,11 +9,22 @@ import kotlinx.serialization.Contextual import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.datetime.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.Json import kotlinx.serialization.json.encodeToJsonElement +import org.bson.BsonDateTime import org.bson.Document import org.bson.codecs.configuration.CodecRegistries import org.bson.codecs.kotlinx.BsonConfiguration +import org.bson.codecs.kotlinx.BsonDecoder +import org.bson.codecs.kotlinx.BsonEncoder import org.bson.codecs.kotlinx.KotlinSerializerCodec import org.bson.codecs.kotlinx.ObjectIdSerializer import org.bson.types.ObjectId @@ -22,6 +34,7 @@ import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance +import java.util.Date @TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class KotlinXSerializationTest { @@ -118,5 +131,52 @@ internal class KotlinXSerializationTest { collection.drop() } + // :snippet-start: kserializer + object InstantAsBsonDateTime : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("InstantAsBsonDateTime", PrimitiveKind.LONG) + + override fun serialize(encoder: Encoder, value: Instant) { + when (encoder) { + is BsonEncoder -> encoder.encodeBsonValue(BsonDateTime(value.toEpochMilliseconds())) + else -> throw SerializationException("Instant is not supported by ${encoder::class}") + } + } + + override fun deserialize(decoder: Decoder): Instant { + return when (decoder) { + is BsonDecoder -> Instant.fromEpochMilliseconds(decoder.decodeBsonValue().asDateTime().value) + else -> throw SerializationException("Instant is not supported by ${decoder::class}") + } + } + } + // :snippet-end: + + @Test + fun customKSerializerTest() = runBlocking { + // :snippet-start: kserializer-dataclass + @Serializable + data class PaintOrder( + val color: String, + val qty: Int, + @Serializable(with = InstantAsBsonDateTime::class) + val orderDate: Instant, + ) + // :snippet-end: + + val collection = database.getCollection("orders") + val paintOrder = PaintOrder("magenta", 5, Instant.parse("2024-01-15T00:00:00Z")) + val insertOneResult = collection.insertOne(paintOrder) + + val resultsFlow = collection.withDocumentClass() + .find(eq(PaintOrder::color.name, "magenta")) + .firstOrNull() + + if (resultsFlow != null) { + assertEquals(resultsFlow["orderDate"], Date.from(java.time.Instant.parse("2024-01-15T00:00:00Z"))) + } + + collection.drop() + } + } diff --git a/source/examples/generated/KotlinXSerializationTest.snippet.kserializer-dataclass.kt b/source/examples/generated/KotlinXSerializationTest.snippet.kserializer-dataclass.kt new file mode 100644 index 00000000..490272a4 --- /dev/null +++ b/source/examples/generated/KotlinXSerializationTest.snippet.kserializer-dataclass.kt @@ -0,0 +1,7 @@ +@Serializable +data class PaintOrder( + val color: String, + val qty: Int, + @Serializable(with = InstantAsBsonDateTime::class) + val orderDate: Instant, +) diff --git a/source/examples/generated/KotlinXSerializationTest.snippet.kserializer.kt b/source/examples/generated/KotlinXSerializationTest.snippet.kserializer.kt new file mode 100644 index 00000000..0f538b81 --- /dev/null +++ b/source/examples/generated/KotlinXSerializationTest.snippet.kserializer.kt @@ -0,0 +1,17 @@ +object InstantAsBsonDateTime : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("InstantAsBsonDateTime", PrimitiveKind.LONG) + + override fun serialize(encoder: Encoder, value: Instant) { + when (encoder) { + is BsonEncoder -> encoder.encodeBsonValue(BsonDateTime(value.toEpochMilliseconds())) + else -> throw SerializationException("Instant is not supported by ${encoder::class}") + } + } + + override fun deserialize(decoder: Decoder): Instant { + return when (decoder) { + is BsonDecoder -> Instant.fromEpochMilliseconds(decoder.decodeBsonValue().asDateTime().value) + else -> throw SerializationException("Instant is not supported by ${decoder::class}") + } + } +} diff --git a/source/fundamentals/data-formats/serialization.txt b/source/fundamentals/data-formats/serialization.txt index 16f40fad..7a1d88d7 100644 --- a/source/fundamentals/data-formats/serialization.txt +++ b/source/fundamentals/data-formats/serialization.txt @@ -189,6 +189,9 @@ Then, you can define your codec using the <{+api+}/apidocs/bson-kotlinx/bson-kotlinx/org.bson.codecs.kotlinx/-kotlin-serializer-codec/-companion/index.html>`__ method and add it to the registry. +Custom Codec Example +~~~~~~~~~~~~~~~~~~~~ + The following example shows how to create a codec using the ``KotlinSerializerCodec.create()`` method and configure it to not encode defaults: @@ -202,6 +205,25 @@ The following example shows how to create a codec using the .. literalinclude:: /examples/generated/KotlinXSerializationTest.snippet.custom-serialization.kt :language: kotlin +Custom Serializer Example +~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can create a custom serializer to handle the conversion of different +types to BSON. The following example shows how to create a custom +``KSerializer`` instance to convert a ``kotlinx.datetime.Instant`` to a +``BsonDateTime``: + +.. literalinclude:: /examples/generated/KotlinXSerializationTest.snippet.kserializer.kt + :language: kotlin + +The following code shows the ``PaintOrder`` data class in which the +``orderDate`` field has an annotation that specifies the custom +serializer class defined in the preceding code: + +.. literalinclude:: /examples/generated/KotlinXSerializationTest.snippet.kserializer-dataclass.kt + :language: kotlin + :emphasize-lines: 5 + For more information about the methods and classes mentioned in this section, see the following API Documentation: