Skip to content

Commit

Permalink
#154 allow direct polymorphism serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
zigzago committed Nov 11, 2019
1 parent 2364170 commit 1585f7e
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.github.jershell.kbson.DateSerializer
import com.github.jershell.kbson.ObjectIdSerializer
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.internal.PairSerializer
import kotlinx.serialization.internal.ReferenceArraySerializer
import kotlinx.serialization.internal.StringSerializer
Expand Down Expand Up @@ -100,7 +102,7 @@ internal object KMongoSerializationRepository {
)

@ImplicitReflectionSerializer
private fun getBaseSerializer(obj: Any): KSerializer<*>? {
private fun <T : Any> getBaseSerializer(obj: T, kClass: KClass<T> = obj.javaClass.kotlin): KSerializer<*>? {
@Suppress("UNCHECKED_CAST")
return when (obj) {
is KProperty<*> -> KPropertySerializer
Expand All @@ -111,19 +113,33 @@ internal object KMongoSerializationRepository {
getSerializer(obj.third)
)
is Array<*> -> ReferenceArraySerializer(
obj.javaClass.kotlin as KClass<Any>,
kClass as KClass<Any>,
obj.filterNotNull().let {
if (it.isEmpty()) StringSerializer else getSerializer(it.first())
} as KSerializer<Any>
)
else -> module.getContextual(obj.javaClass.kotlin)
?: module.getPolymorphic(obj.javaClass.kotlin, obj)
else -> module.getContextual(kClass)
?: module.getPolymorphic(kClass, obj)?.let {
PolymorphicSerializer(kClass)
}
}
}

@Suppress("UNCHECKED_CAST")
@ImplicitReflectionSerializer
fun <T : Any> getSerializer(obj: T?): KSerializer<T> =
fun <T : Any> getSerializer(kClass: KClass<T>, obj: T?): KSerializer<T> =
if (obj == null) {
JsonNullSerializer as? KSerializer<T> ?: error("no serializer for null")
} else {
(serializersMap[kClass]
?: getBaseSerializer(obj, kClass)
?: kClass.serializer()) as? KSerializer<T>
?: error("no serializer for $obj of class $kClass")
}

@Suppress("UNCHECKED_CAST")
@ImplicitReflectionSerializer
private fun <T : Any> getSerializer(obj: T?): KSerializer<T> =
if (obj == null) {
JsonNullSerializer as? KSerializer<T> ?: error("no serializer for null")
} else {
Expand All @@ -138,7 +154,16 @@ internal object KMongoSerializationRepository {
fun <T : Any> getSerializer(kClass: KClass<T>): KSerializer<T> =
(serializersMap[kClass]
?: module.getContextual(kClass)
?: kClass.serializer()) as? KSerializer<T>
?: try {
kClass.serializer()
} catch (e: SerializationException) {
if (kClass.isAbstract || kClass.isOpen || kClass.isSealed) {
PolymorphicSerializer(kClass)
} else {
throw e
}
}
) as? KSerializer<T>
?: error("no serializer for $kClass of class $kClass")

@Volatile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ internal class SerializationCodec<T : Any>(val clazz: KClass<T>) : CollectibleCo

@ImplicitReflectionSerializer
override fun encode(writer: BsonWriter, value: T, encoderContext: EncoderContext) {
val serializer = KMongoSerializationRepository.getSerializer(value)
val serializer = KMongoSerializationRepository.getSerializer(clazz, value)
BsonEncoder(writer, module, Configuration()).encode(serializer, value)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,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.assertFails
import kotlin.test.assertFalse

/**
Expand Down Expand Up @@ -151,7 +152,7 @@ class SerializationCodecTest {
data class IntMessage(val number: Int) : Message

@Serializable
data class Container(val m:Message)
data class Container(val m: Message)

@ImplicitReflectionSerializer
@Test
Expand All @@ -174,4 +175,72 @@ class SerializationCodecTest {
assertEquals(c, newC)
}

interface Message2

@Serializable
data class StringMessage2(val message: String) : Message2

@Serializable
data class IntMessage2(val number: Int) : Message2

@ImplicitReflectionSerializer
@Test
fun `encode and decode directly custom polymorphic serializer`() {
registerModule(
SerializersModule {
polymorphic(Message2::class) {
StringMessage2::class with StringMessage2.serializer()
IntMessage2::class with IntMessage2.serializer()
}
})
val c = StringMessage2("a")
val codec = SerializationCodec(Message2::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)
}

@Serializable
data class SerializableClass(val s: String)

data class NotSerializableClass(val s: String)

@ImplicitReflectionSerializer
@Test
fun `encoding a not serializable class throws a SerializationException`() {
val t = assertFails {
val c = NotSerializableClass("a")
val codec = SerializationCodec(NotSerializableClass::class)
val document = BsonDocument()
val writer = BsonDocumentWriter(document)
codec.encode(writer, c, EncoderContext.builder().build())
}
assertEquals(
"Can't locate argument-less serializer for class org.litote.kmongo.serialization.SerializationCodecTest\$NotSerializableClass. For generic classes, such as lists, please provide serializer explicitly.",
t.message
)
}

@ImplicitReflectionSerializer
@Test
fun `decoding a not serializable class throws a SerializationException`() {
val t = assertFails {
val c = SerializableClass("a")
val codec = SerializationCodec(SerializableClass::class)
val document = BsonDocument()
val writer = BsonDocumentWriter(document)
codec.encode(writer, c, EncoderContext.builder().build())

val codec2 = SerializationCodec(NotSerializableClass::class)
codec2.decode(BsonDocumentReader(document), DecoderContext.builder().build())
}
assertEquals(
"Can't locate argument-less serializer for class org.litote.kmongo.serialization.SerializationCodecTest\$NotSerializableClass. For generic classes, such as lists, please provide serializer explicitly.",
t.message
)
}
}

0 comments on commit 1585f7e

Please sign in to comment.