kotlinx.serialization
kotlinx.serialization copied to clipboard
Kotlinx deserialization does not include properties from abstract class when using “JsonContentPolymorphicSerializer”
Describe the bug
When using JsonContentPolymorphicSerializer to deserialize class which extends abstract class, properties from super class are missing.
To Reproduce
Lets say there is abstract class BaseClass ...
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable(with = PolymorphicSerializer::class)
abstract class BaseClass {
@SerialName("bid")
var baseId: String? = null
}
... class ExtendedClass that extends it ...
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
class ExtendedClass(
@SerialName("eid")
var newId: String? = null
) : BaseClass()
... and PolymorphicSerializer
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
object PolymorphicSerializer : JsonContentPolymorphicSerializer<BaseClass>(BaseClass::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out BaseClass> {
return ExtendedClass.serializer()
}
}
PolymorphicSerializer is used because JSON can be deserialized as different classes that extend BaseClass based on some property (for sake of simplicity I just used ExtendedClass.serializer() )
Now, if we try to deserialize generic JSON to ExtendedClass ...
class DeserializationTest {
@Test
fun deserialization_isCorrect() {
val extendedClass = Json {
ignoreUnknownKeys = true
}.decodeFromString<BaseClass>("""{"bid":"bid","eid":"eid"}""")
assertEquals("bid", extendedClass.baseId)
}
}
... test fails
java.lang.AssertionError: Expected :bid Actual :null
If I try to serialize ExtendedClass ...
val extendedClass = ExtendedClass()
extendedClass.baseId = "bid"
extendedClass.newId = "eid"
val encodedJson = Json {
ignoreUnknownKeys = true
}.encodeToString(extendedClass)
I can see encodedJson is {"eid":"eid"} but if I remove JsonContentPolymorphicSerializer this works fine (replace @Serializable(with = PolymorphicSerializer::class) with @Serializable ), encodedJson is {"bid":"bid","eid":"eid"}
So, for some reason JsonContentPolymorphicSerializer does not take into account properties from super class.
Expected behavior
JsonContentPolymorphicSerializer should take into account properties from super class, that is, extendedClass.baseId should not be null, it should be "bid".
I managed to solve this by declaring baseId as abstract:
@SerialName("bid")
abstract var baseId: String?
and then in ExtendedClass
@SerialName("bid")
override var baseId: String?
But that solution is a bit ugly considering the fact we need to define it in every class that extends BaseClass. Not sure if this is a bug, but it would be nice to have solution that does not require that.
Environment
- Kotlin version: 1.4.32
- Library version: 1.1.0
- Kotlin platforms: JVM
- Gradle version: 4.1.3
- IDE version: Android Studio 4.1.3
- Other relevant context: MacOS 11.0.1, Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
We are having the same issue (with a sealed base class), the workaround is quite ugly, so a fix would be great :)
- Kotlin version: 1.5.10
- Library version: 1.1.0
- Kotlin platforms: JVM
- Other relevant context: Windows 10
I'm running into the same kind of design issue with JsonContentPolymorphicSerializer
.
It makes working with large hierarchies with several shared properties almost impossible without writing lots of ugly code repetitions. Any updates on this? Thank you! 🙏
What's more, not only properties need to be overridden on each subclass, but any @SerialName
or @Serializable
annotation also needs to be repeated 🤦
I think the root of the problem here is @Serializable(with = PolymorphicSerializer::class)
on the BaseClass
itself. See, normally ExtendedClass (de)serializer would delegate to the generated serializer of the BaseClass for filling its own properties (including private ones). When Serializable annotation is applied with serializer override plugin does not do this delegation anymore, since it simply doesn't know what kind of serializer is used for the base class. Hence, the base class properties are lost.
I think this is one more use-case for #1169.
In the meantime, a workaround would be to avoid @Serializable(with = MyPolymorphicSerializer::class)
directly on the class and use other methods of passing serializer instead (@file:UseSerializers
, etc)