Skip to content

Commit 0d8cc80

Browse files
authored
Handle kotlin / JVM erasure of types (#1295)
* Handle kotlin / JVM erasure of types It has been observed that the Kotlin can behave differently during reflection and return Type<Object> where normally it would report the correct type. Checking for erasure allows the bson-kotlin library to handle these cases and return the expected type. JAVA-5292
1 parent d80e9c1 commit 0d8cc80

File tree

3 files changed

+43
-1
lines changed

3 files changed

+43
-1
lines changed

bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt

+10-1
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ import kotlin.reflect.KParameter
2323
import kotlin.reflect.KProperty1
2424
import kotlin.reflect.KType
2525
import kotlin.reflect.KTypeParameter
26+
import kotlin.reflect.KTypeProjection
2627
import kotlin.reflect.full.createType
2728
import kotlin.reflect.full.findAnnotation
2829
import kotlin.reflect.full.findAnnotations
2930
import kotlin.reflect.full.hasAnnotation
3031
import kotlin.reflect.full.primaryConstructor
3132
import kotlin.reflect.jvm.javaType
33+
import kotlin.reflect.jvm.jvmErasure
3234
import org.bson.BsonReader
3335
import org.bson.BsonType
3436
import org.bson.BsonWriter
@@ -199,7 +201,7 @@ internal data class DataClassCodec<T : Any>(
199201
codecRegistry.getCodec(
200202
kParameter,
201203
(kParameter.type.classifier as KClass<Any>).javaObjectType,
202-
kParameter.type.arguments.mapNotNull { typeMap[it.type] ?: it.type?.javaType }.toList())
204+
kParameter.type.arguments.mapNotNull { typeMap[it.type] ?: computeJavaType(it) }.toList())
203205
}
204206
is KTypeParameter -> {
205207
when (val pType = typeMap[kParameter.type] ?: kParameter.type.javaType) {
@@ -219,6 +221,13 @@ internal data class DataClassCodec<T : Any>(
219221
"Could not find codec for ${kParameter.name} with type ${kParameter.type}")
220222
}
221223

224+
private fun computeJavaType(kTypeProjection: KTypeProjection): Type? {
225+
val javaType: Type = kTypeProjection.type?.javaType!!
226+
return if (javaType == Any::class.java) {
227+
kTypeProjection.type?.jvmErasure?.javaObjectType
228+
} else javaType
229+
}
230+
222231
@Suppress("UNCHECKED_CAST")
223232
private fun CodecRegistry.getCodec(kParameter: KParameter, clazz: Class<Any>, types: List<Type>): Codec<Any> {
224233
val codec =

bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt

+30
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,22 @@ import kotlin.test.assertEquals
2020
import kotlin.test.assertNotNull
2121
import kotlin.test.assertNull
2222
import kotlin.test.assertTrue
23+
import kotlin.time.Duration
24+
import org.bson.BsonReader
25+
import org.bson.BsonWriter
26+
import org.bson.codecs.Codec
27+
import org.bson.codecs.DecoderContext
28+
import org.bson.codecs.EncoderContext
2329
import org.bson.codecs.configuration.CodecConfigurationException
30+
import org.bson.codecs.configuration.CodecRegistries.fromCodecs
31+
import org.bson.codecs.configuration.CodecRegistries.fromProviders
32+
import org.bson.codecs.configuration.CodecRegistries.fromRegistries
2433
import org.bson.codecs.kotlin.samples.DataClassParameterized
34+
import org.bson.codecs.kotlin.samples.DataClassWithJVMErasure
2535
import org.bson.codecs.kotlin.samples.DataClassWithSimpleValues
2636
import org.bson.conversions.Bson
2737
import org.junit.jupiter.api.Test
38+
import org.junit.jupiter.api.assertDoesNotThrow
2839
import org.junit.jupiter.api.assertThrows
2940

3041
class DataClassCodecProviderTest {
@@ -59,4 +70,23 @@ class DataClassCodecProviderTest {
5970
assertTrue { codec is DataClassCodec }
6071
assertEquals(DataClassWithSimpleValues::class.java, codec.encoderClass)
6172
}
73+
74+
@Test
75+
fun shouldBeAbleHandleDataClassWithJVMErasure() {
76+
77+
class DurationCodec : Codec<Duration> {
78+
override fun encode(writer: BsonWriter, value: Duration, encoderContext: EncoderContext) = TODO()
79+
override fun getEncoderClass(): Class<Duration> = Duration::class.java
80+
override fun decode(reader: BsonReader, decoderContext: DecoderContext): Duration = TODO()
81+
}
82+
83+
val registry =
84+
fromRegistries(
85+
fromCodecs(DurationCodec()), fromProviders(DataClassCodecProvider()), Bson.DEFAULT_CODEC_REGISTRY)
86+
87+
val codec = assertDoesNotThrow { registry.get(DataClassWithJVMErasure::class.java) }
88+
assertNotNull(codec)
89+
assertTrue { codec is DataClassCodec }
90+
assertEquals(DataClassWithJVMErasure::class.java, codec.encoderClass)
91+
}
6292
}

bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.bson.codecs.kotlin.samples
1717

18+
import kotlin.time.Duration
1819
import org.bson.BsonDocument
1920
import org.bson.BsonMaxKey
2021
import org.bson.BsonType
@@ -159,3 +160,5 @@ data class DataClassWithFailingInit(val id: String) {
159160
}
160161

161162
data class DataClassWithSequence(val value: Sequence<String>)
163+
164+
data class DataClassWithJVMErasure(val duration: Duration, val ints: List<Int>)

0 commit comments

Comments
 (0)