Skip to content

Commit

Permalink
fixes #154 register custom serializers for kotlinx.serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
zigzago committed Nov 11, 2019
1 parent 77c65a6 commit 2364170
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,28 @@ import java.util.Calendar
import java.util.Date
import java.util.GregorianCalendar
import java.util.Locale
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
import kotlin.reflect.KClass
import kotlin.reflect.KProperty

@PublishedApi
internal val customSerializersMap: MutableMap<KClass<*>, KSerializer<*>> = ConcurrentHashMap()
private val customModules = CopyOnWriteArraySet<SerialModule>()

/**
* Add a custom [SerialModule] to KMongo kotlinx.serialization mapping.
*/
fun registerModule(module: SerialModule) {
customModules.add(module)
}

/**
* Add a custom serializer to KMongo kotlinx.serialization mapping
*/
inline fun <reified T> registerSerializer(serializer: KSerializer<T>) {
customSerializersMap[T::class] = serializer
}

/**
*
Expand Down Expand Up @@ -97,7 +116,8 @@ internal object KMongoSerializationRepository {
if (it.isEmpty()) StringSerializer else getSerializer(it.first())
} as KSerializer<Any>
)
else -> null
else -> module.getContextual(obj.javaClass.kotlin)
?: module.getPolymorphic(obj.javaClass.kotlin, obj)
}
}

Expand All @@ -107,15 +127,42 @@ internal object KMongoSerializationRepository {
if (obj == null) {
JsonNullSerializer as? KSerializer<T> ?: error("no serializer for null")
} else {

//TODO don't known yet how to do this without reflection
(serializersMap[obj.javaClass.kotlin]
?: getBaseSerializer(obj)
?: obj.javaClass.kotlin.serializer()) as? KSerializer<T>
?: error("no serializer for $obj of class ${obj.javaClass.kotlin}")
}

val module: SerialModule = SerializersModule {
@Suppress("UNCHECKED_CAST")
@ImplicitReflectionSerializer
fun <T : Any> getSerializer(kClass: KClass<T>): KSerializer<T> =
(serializersMap[kClass]
?: module.getContextual(kClass)
?: kClass.serializer()) as? KSerializer<T>
?: error("no serializer for $kClass of class $kClass")

@Volatile
private var baseModule: SerialModule = SerializersModule {
include(serializersModuleOf(serializersMap))
include(serializersModuleOf(customSerializersMap))
customModules.forEach { include(it) }
}
@Volatile
private var customModulesSize: Int = customModules.size
@Volatile
private var customSerializersSize: Int = customSerializersMap.size

val module: SerialModule
get() {
if (customSerializersSize != customSerializersMap.size || customModulesSize != customModules.size) {
customSerializersSize = customSerializersMap.size
customModulesSize = customModules.size
baseModule = SerializersModule {
include(serializersModuleOf(serializersMap))
include(serializersModuleOf(customSerializersMap))
customModules.forEach { include(it) }
}
}
return baseModule
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import com.github.jershell.kbson.Configuration
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.decode
import kotlinx.serialization.encode
import kotlinx.serialization.serializer
import org.bson.BsonDouble
import org.bson.BsonInt32
import org.bson.BsonInt64
Expand Down Expand Up @@ -61,7 +60,9 @@ internal class SerializationCodec<T : Any>(val clazz: KClass<T>) : CollectibleCo

@ImplicitReflectionSerializer
override fun decode(reader: BsonReader, decoderContext: DecoderContext): T {
return BsonDocumentDecoder(reader, module, Configuration()).decode(clazz.serializer())
return BsonDocumentDecoder(reader, module, Configuration()).decode(
KMongoSerializationRepository.getSerializer(clazz)
)
}

override fun getDocumentId(document: T): BsonValue {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package org.litote.kmongo.serialization

import kotlinx.serialization.CompositeDecoder
import kotlinx.serialization.CompositeEncoder
import kotlinx.serialization.ContextualSerialization
import kotlinx.serialization.Decoder
import kotlinx.serialization.Encoder
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import kotlinx.serialization.internal.SerialClassDescImpl
import kotlinx.serialization.internal.StringDescriptor
import kotlinx.serialization.modules.SerializersModule
import org.bson.BsonDocument
import org.bson.BsonDocumentReader
import org.bson.BsonDocumentWriter
Expand All @@ -14,6 +23,7 @@ import org.litote.kmongo.Id
import org.litote.kmongo.model.Friend
import org.litote.kmongo.newId
import kotlin.test.assertEquals
import kotlin.test.assertFalse

/**
*
Expand All @@ -29,7 +39,6 @@ class SerializationCodecTest {
val writer = BsonDocumentWriter(document)
codec.encode(writer, friend, EncoderContext.builder().build())

println(document)
val newFriend = codec.decode(BsonDocumentReader(document), DecoderContext.builder().build())

assertEquals(friend, newFriend)
Expand All @@ -47,7 +56,6 @@ class SerializationCodecTest {
val writer = BsonDocumentWriter(document)
codec.encode(writer, id, EncoderContext.builder().build())

println(document)
val newFriend = codec.decode(BsonDocumentReader(document), DecoderContext.builder().build())

assertEquals(id, newFriend)
Expand All @@ -65,7 +73,6 @@ class SerializationCodecTest {
val writer = BsonDocumentWriter(document)
codec.encode(writer, idList, EncoderContext.builder().build())

println(document)
val newFriend = codec.decode(BsonDocumentReader(document), DecoderContext.builder().build())

assertEquals(idList, newFriend)
Expand All @@ -86,9 +93,85 @@ class SerializationCodecTest {
val writer = BsonDocumentWriter(document)
codec.encode(writer, idList, EncoderContext.builder().build())

println(document)
val newFriend = codec.decode(BsonDocumentReader(document), DecoderContext.builder().build())

assertEquals(idList, newFriend)
}

data class Custom(val s: String, val b: Boolean = false)

@Serializer(forClass = Custom::class)
class CustomSerializer : KSerializer<Custom> {

object CustomClassDesc : SerialClassDescImpl("Custom") {
init {
addElement("s")
pushDescriptor(StringDescriptor)
}
}

override fun deserialize(decoder: Decoder): Custom {
decoder as CompositeDecoder
decoder.beginStructure(CustomClassDesc)
val c = Custom(decoder.decodeStringElement(CustomClassDesc, 0))
decoder.endStructure(CustomClassDesc)
return c
}

override fun serialize(encoder: Encoder, obj: Custom) {
encoder as CompositeEncoder
encoder.beginStructure(CustomClassDesc)
encoder.encodeStringElement(CustomClassDesc, 0, obj.s)
encoder.endStructure(CustomClassDesc)
}
}

@ImplicitReflectionSerializer
@Test
fun `encode and decode with custom serializer`() {
registerSerializer(CustomSerializer())
val c = Custom("a", true)
val codec = SerializationCodec(Custom::class)
val document = BsonDocument()
val writer = BsonDocumentWriter(document)
codec.encode(writer, c, EncoderContext.builder().build())

val newC = codec.decode(BsonDocumentReader(document), DecoderContext.builder().build())

assertEquals(c.s, newC.s)
assertFalse(newC.b)
}

interface Message

@Serializable
data class StringMessage(val message: String) : Message

@Serializable
data class IntMessage(val number: Int) : Message

@Serializable
data class Container(val m:Message)

@ImplicitReflectionSerializer
@Test
fun `encode and decode with custom polymorphic serializer`() {
registerModule(
SerializersModule {
polymorphic(Message::class) {
StringMessage::class with StringMessage.serializer()
IntMessage::class with IntMessage.serializer()
}
})
val c = Container(StringMessage("a"))
val codec = SerializationCodec(Container::class)
val document = BsonDocument()
val writer = BsonDocumentWriter(document)
codec.encode(writer, c, EncoderContext.builder().build())

val newC = codec.decode(BsonDocumentReader(document), DecoderContext.builder().build())

assertEquals(c, newC)
}

}

0 comments on commit 2364170

Please sign in to comment.