Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properties inherit from parent class which use custom serializer not serialized #1043

Open
rwsbillyang opened this issue Sep 6, 2020 · 7 comments
Labels

Comments

@rwsbillyang
Copy link

rwsbillyang commented Sep 6, 2020

Describe the bug
The properties inherit from parent class are not serialized if parent class use custom serializer.

To Reproduce
If use default serializer generated by plugin, works fine:

{"msg":null,"code":0}
{"msg":null,"code":0,"content":"some content"}

If use custom serializer, derived class lack of properties from base class:

{"msg8":null,"msg8":0} //works fine
{"content":"some content"} //Expect: {"msg8":null,"code8":0,"content":"some content"}

The test code:

@Serializable(with = Box8Serializer::class)
//@Serializable
open class Box8 { //if use sealed instead of open, same result
    var msg: String? = null
    var code: Int = 0
}

// if put properties of parent class in its constructor reports:
//Impossible to make this class serializable because its parent is not serializable and does not have exactly one constructor without parameters
@Serializable
class TheTextMsg(val content: String) : Box8()

fun testCustomSerializerAndInheritance() {
    val box = Box8()
    val textMsg = TheTextMsg("some content")
    val json = Json {
         // same result
//        serializersModule = SerializersModule {
//            polymorphic(Box8::class) {
//                subclass(TheTextMsg::class)
//            }
//        }
    }

    println(json.encodeToString(box))
    println(json.encodeToString(textMsg))

    // if parent class use serializer generated by plugin
    //{"msg":null,"code":0}
    //{"msg":null,"code":0,"content":"some content"}

    //if use custom serializer, output:
    //{"msg8":null,"msg8":0}
    //{"content":"some content"}
}


object Box8Serializer : KSerializer<Box8> {
    override val descriptor: SerialDescriptor =
            buildClassSerialDescriptor("box") {
                element<String?>("msg8", isOptional = true)
                element<Int?>("code8", isOptional = true)
            }

    @ExperimentalSerializationApi
    override fun serialize(encoder: Encoder, value: Box8) =
            encoder.encodeStructure(descriptor) {
                encodeNullableSerializableElement(descriptor, 0, String.serializer(), value.msg)
                encodeIntElement(descriptor, 1, value.code)
            }

    override fun deserialize(decoder: Decoder): Box8 {
        TODO("Not implement")
    }
}

Expected behavior
using custom serializer when serialize

Environment

  • Kotlin version: 1.4.0
  • Library version: 1.0.0-RC
  • Kotlin platforms: JVM
  • Gradle version: 6.3
  • IDE version: IntellijIDEA 2020.2.1
  • Other relevant context : MacOS 10.12.6, JDK 1.8.0_261
@sandwwraith
Copy link
Member

Indeed, plugin-generated serializers for inheritors usually delegate to parents' serializers — but in case of custom serializer plugin doesn't know how to delegate. So, the workaround for now is to write custom serializer for inheritor too.

Btw, your custom serializer is incorrect: indices in encode...Element should be different, so value.msg should have index 0 and value.code index 1

@rwsbillyang
Copy link
Author

Indeed, plugin-generated serializers for inheritors usually delegate to parents' serializers — but in case of custom serializer plugin doesn't know how to delegate. So, the workaround for now is to write custom serializer for inheritor too.

Btw, your custom serializer is incorrect: indices in encode...Element should be different, so value.msg should have index 0 and value.code index 1

Thank you for your explantation. The limitation results in writing custom serializer for every child class. One solution is to use composite instead of inheritance, but possibly there is a limitation either: the data class should be consistent with JSON structure defined by 3rd-party API provider.

I corrected the index in my demo code.

@thol01
Copy link

thol01 commented Jan 12, 2021

Is this issue on any road map to be fixed?

/Thomas

@pdvrieze
Copy link
Contributor

I don't think that it can be "fixed" at all. The only thing that could be done is for the serializer of the child type to pretend that the parent would be serialized by default (but why then use a custom serializer). As to supporting inheritance, for serialization you would probably want to have a protected function that would write only the properties, but not the structure (the bit in encodeStructure) and then call that from the encodeStructure in the child serializer. For decoding you could have the child only have element indices that don't overlap with the inherited properties and delegate handling of the parent index range to a function in the parent.

Btw. for naming just use @SerialName instead of a custom serializer.

@thol01
Copy link

thol01 commented Jan 12, 2021

Thanks for the quick reply. We have a generic Response (Kotlin implementation of Google JSON Style Guide https://google.github.io/styleguide/jsoncstyleguide.xml) that all our Micro Services uses a a payload base. So all public payload inherits from this Response class. In the Response class we have some computed properties that we would like to serialize so that is why I created my own KSerializer.
Do you know if there is a better Kotlin way to do this that will work with Kotlin serialization? Should we go with delegation instead of inheritance?

@pdvrieze
Copy link
Contributor

@thol01 The way I see this is that conceptually you would transform the Response instance into a model for serialization. The shortcut is to serialize using a custom serializer for the parent where you serialize the properties directly.

@thol01
Copy link

thol01 commented Jan 13, 2021

Thanks 🤩

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants