kotlinx.serialization
kotlinx.serialization copied to clipboard
IndexOutOfBoundsException by getElementDescriptor with @Serializer
Describe the bug
When using @Serializer, the following exception is thrown when trying to get an element descriptor.
java.lang.IndexOutOfBoundsException: Example descriptor has only 1 elements, index: 0
To Reproduce
data class Example(
val value: String
)
@Serializer(Example::class)
object ExampleSerializer
fun main() {
println(ExampleSerializer.descriptor.getElementName(0)) // Everything is fine here
println(ExampleSerializer.descriptor.getElementDescriptor(0)) // IndexOutOfBoundsException
}
Expected behavior
Method SerialDescriptor::getElementDescriptor should return a SerialDescriptor.
Environment
- Kotlin version: 1.3.72
- Library version: 0.20.0
- Kotlin platforms: JVM
- Gradle version: 6.4.1
@sandwwraith this is a bug in the compiler plugin:
@Serializable
data class Example(val value: String)
@Serializer(Example::class)
object ExampleSerializer
In this example, Example.serializer().descriptor and ExampleSerializer.descriptor() have different structure. Please ensure that such descriptors are generated by the same code
@qwwdfsad Your case is not the same as in the original issue. When Example annotated as Serializable, the serializer is also generated inside. It should be like
@Serializable(ExampleSerializer::class)
data class Example(val value: String)
@Serializer(Example::class)
object ExampleSerializer
I know, I've deliberately exposed it.
Auto-generated serializer in nested companion (@Serializable data class ...) and auto-generated external serializer (@Serializer(Example::class)) have different SerialDescriptor structures.
Two bugs here:
- These descriptors are generated by different code paths (it should be the same code path)
- The latter codegen has the bug
While we all waiting for this to be fixed I've just found a workaround that worked for me. I wanted to delegate to auto-generated serializer and just add some logic like this:
@Serializable
data class MyDTO(
val someField: String
)
object MyDTOCustomSerializer: KSerializer<MyDTO> by MyDTO.serializer() {
override fun deserialize(decoder: Decoder): MyDTO {
// do stuff
// delegate deserialization to generated serializer
return MyDTO.serializer().deserialize(decoder)
}
}
Though I didn't find a way to make framework use my MyDTOCustomSerializer, if I put it in @Serializable annotation it will just create circular reference. My workaround is using type alias:
@Serializable
data class MyDTOHidden(
val someField: String
)
typealias MyDTO = @Serializable(MyDTOCustomSerializer::class) MyDTOHidden
object MyDTOCustomSerializer: KSerializer<MyDTO> by MyDTOHidden.serializer() {
override fun deserialize(decoder: Decoder): MyDTO {
// do stuff
// delegate deserialization to original serializer
return MyDTOHidden.serializer().deserialize(decoder)
}
}
Am I overthinking it?
@AngryGami That's an adequate solution. You can also vote for #1169.