Skip to content

Commit

Permalink
Fix serializer lookup by KType for third party generics (#1397)
Browse files Browse the repository at this point in the history
This lookup used to fail, because `SerializersModule.serializerByKTypeImpl()`
never tried `getContextual()` after constructSerializerForGivenTypeArgs() fails
to find a serializer (and that *will* fail, because `ThirdPartyBox` is not
annotated as @serializable, since it is a third party class).

This does work when doing the look up by Java type with
`serializerByJavaTypeImpl.serializerByJavaTypeImpe()`.

This commit adds the call to `reflectiveOrContextual()` to
`SerializersModule.serializerByKTypeImpl()`.

The accompanying test illustrates the scenario where this used to fail.
  • Loading branch information
mvdbos authored Apr 13, 2021
1 parent 603c85f commit 83d0faa
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 5 deletions.
7 changes: 7 additions & 0 deletions core/commonMain/src/kotlinx/serialization/Serializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,17 @@ private fun SerializersModule.builtinSerializer(
return ArraySerializer<Any, Any?>(typeArguments[0].classifier as KClass<Any>, serializers[0]).cast()
}
rootClass.constructSerializerForGivenTypeArgs(*serializers.toTypedArray())
?: reflectiveOrContextual(rootClass)
}
}
}

@OptIn(ExperimentalSerializationApi::class)
internal fun <T : Any> SerializersModule.reflectiveOrContextual(kClass: KClass<T>): KSerializer<T>? {
return kClass.serializerOrNull() ?: getContextual(kClass)
}


/**
* Retrieves a [KSerializer] for the given [KClass].
* The given class must be annotated with [Serializable] or be one of the built-in types.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package kotlinx.serialization.features

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.modules.*
import kotlin.test.*

open class ThirdPartyGenericsTest {
// This is a 3rd party class that we can't annotate as @Serializable
data class ThirdPartyBox<T>(val contents: T)

// This is the item that we put in the ThirdPartyBox, we control it, so can annotate it
@Serializable
data class Item(val name: String)

// The serializer for the ThirdPartyBox<T>
class BoxSerializer<T>(dataSerializer: KSerializer<T>) : KSerializer<ThirdPartyBox<T>> {
@Serializable
data class BoxSurrogate<T>(val contents: T)

private val strategy = BoxSurrogate.serializer(dataSerializer)
override val descriptor: SerialDescriptor = strategy.descriptor

override fun deserialize(decoder: Decoder): ThirdPartyBox<T> {
return ThirdPartyBox(decoder.decodeSerializableValue(strategy).contents)
}

override fun serialize(encoder: Encoder, value: ThirdPartyBox<T>) {
encoder.encodeSerializableValue(strategy, BoxSurrogate(value.contents))
}
}

// Register contextual serializer for ThirdPartyBox<Item>
protected val boxWithItemSerializer = BoxSerializer(Item.serializer())
protected val serializersModule = SerializersModule {
contextual(boxWithItemSerializer)
}

@Test
fun testSurrogateSerializerFoundForGenericWithKotlinType() {
val serializer = serializersModule.serializer<ThirdPartyBox<Item>>()
assertEquals(boxWithItemSerializer.descriptor, serializer.descriptor)
}
}
5 changes: 0 additions & 5 deletions core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,6 @@ private fun SerializersModule.genericArraySerializer(
return ArraySerializer(kclass, serializer) as KSerializer<Any>
}

@OptIn(ExperimentalSerializationApi::class)
private fun <T : Any> SerializersModule.reflectiveOrContextual(kClass: KClass<T>): KSerializer<T>? {
return kClass.serializerOrNull() ?: getContextual(kClass)
}

private fun Type.kclass(): KClass<*> = when (val it = this) {
is KClass<*> -> it
is Class<*> -> it.kotlin
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kotlinx.serialization.features

import kotlinx.serialization.*
import kotlin.test.*

class JvmThirdPartyGenericsTest : ThirdPartyGenericsTest() {
@Test
fun testSurrogateSerializerFoundForGenericWithJavaType() {
val filledBox = ThirdPartyBox(contents = Item("Foo"))
val serializer = serializersModule.serializer(filledBox::class.java)
assertEquals(boxWithItemSerializer.descriptor, serializer.descriptor)
}
}

0 comments on commit 83d0faa

Please sign in to comment.