Kotlinx.serialization: Serialization of abstract (overridden) class members fails

Created on 31 Jan 2018  Â·  13Comments  Â·  Source: Kotlin/kotlinx.serialization

Serialization of a class extending from an abstract class currently does not seem to be supported (or at least not 'out of the box').

In the code sample below, serialization succeeds but the overriden member id gets serialized twice. The serialized object becomes ["test.PolymorphicJSONTest.B",{"id":5,"id":5}].

Parsing the serialized string results in a MissingFieldException:

kotlinx.serialization.MissingFieldException: Field id is required, but it was missing

The following code reproduces the problem.

@Serializable
abstract class A
{
    abstract val id: Int
}

@Serializable
data class B( override val id: Int ) : A()

@Test
fun testInheritanceJSON() {
    val context = SerialContext().apply {
        registerSerializer( B::class.qualifiedName!!, B::class.serializer() )
    }
    val json = JSON( context = context )

    val obj = B( 5 )
    val serialized = json.stringify( PolymorphicSerializer, obj )
    val deserialized = json.parse( PolymorphicSerializer, serialized )
    assertEquals( obj, deserialized )
}
bug compiler-plugin

Most helpful comment

@daberni Seems like you are using the wrong @Transient annotation. Use @kotlinx.serialization.Transient instead.

All 13 comments

Removing @Serializable from class A does seems to work, but only works when it has a 'no arg constructor'. Otherwise, the following error is given:

Error:Kotlin: [Internal Error] java.lang.IllegalArgumentException: Non-serializable parent of serializable class B must have no arg constructor

This compilation error goes away when @Serializable is applied, but the same error as reported on initially reappears.

Lastly, I tried applying @Transient to the abstract class member, without applying it to the extending class:

    @Serializable
    abstract class A( val test: String ) : Immutable()
    {
        @Transient
        abstract val id: Int
    }

   @Serializable
   data class B( override val id: Int ) : A()

This seems to work and is the behavior I initially anticipated. Is this by design, or a bug?

I tried your approach of applying the @Transient attribute but I get the following error:

This annotation is not applicable to target 'member property without backing field or delegate'

Any idea on this?

@daberni As the error indicates, I'm guessing you are trying to apply @Transient to a class member which does not have state (how I read 'backing field')? Perhaps a getter? Don't really know what the desired behavior here is. Showing some code would be helpful, as well as a description of the behavior you expect.

This old issue seems related.

My class looks almost completely same like yours

@Serializable
abstract class AbstractConstantField {

    @Transient
    abstract val type: String
    var superVariable = "super text"
}

@Serializable
class ConstantField : AbstractConstantField() {

    override val type: String = "Constant"
    var variable: String? = "some text"
}

I wasn't expecting anything, but you said it would work for you. It would help me if this would work 😅

@daberni Seems like you are using the wrong @Transient annotation. Use @kotlinx.serialization.Transient instead.

I was also having this issue and using @kotlinx.serialization.Transient fixed it for me. Intellij was importing the other @Transient by default.

Adding the @Transient attribute in order to prevent overridden members from being serialized twice no longer seems necessary in v0.5.1.

Can this be closed, @sandwwraith? Or was this only fixed accidentally? :)

Neglect that last comment. I must have tested something incorrectly; this issue still persists in the latest version (0.6.2).

Still persists.
Any work around?

@scinfu Read my comment from February last year: https://github.com/Kotlin/kotlinx.serialization/issues/75#issuecomment-362212134

Fixed in Kotlin 1.3.30 – abstract properties are automatically transient

Same issue with overridden properties: If a base class has a property min, and is serializable, it must be in the primary constructor. If a superclass then overrides it, the properties must be again in the primary constructor, and as of now I must mark one of them as transient which is not necessary as both are backed by the same property.

There is a section of the guide now dedicated to the design of serializable hierarchies with overridden properties in particular: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#closed-polymorphism

Was this page helpful?
0 / 5 - 0 ratings