Serializing a class hierarchy with constructor params?

I’m trying to serialize some classes that look like:

abstract class Thing(val type: ThingType) { ... }
abstract class ThingImpl(type: ThingType) :Thing(type) { ... }
class SpecificThing1() : ThingImpl(ThingType.Specific1) { ... }

They’re all marked with @Serializable and the root class is marked with @Polymorphic.

The compiler doesn’t like ThingImpl. It says:

This class is not serializable automatically because it has primary constructor parameters of which are not properties

Mmm. But the parameter is a property. It’s passed to the base class constructor and it’s defined as a property there. Maybe I have to write a custom serializer for this class?

I’m guessing you are using kotlinx.serialization. This library is still in alpha. I suggest looking through the issues on github and adding your usecase there.
Otherwise I guess you will need to write a custom serializer.

If you are jvm only and looking for a stable solution there are a few other libraries out there. I can recommend moshi. It’s json only but it supports kotlin nullability, default parameters and works both with reflection and code gen (annotation processor).

1 Like

Yes, I should have mentioned that. I’m trying to use kotlinx.serialization.

@rnikander While kotlinx.serialization is still not stable this doesn’t mean that it generates poor quality code. It means that the API is not stable yet. That matters mainly if you write custom serializers or your own formats. It also means that there are some features that are not implemented yet and unfortunately that the error messages by the compiler plugin can “be improved”. In my experience, the bug you have is easiest dealt with by having your base classes not use a primary constructor, but have multiple - one of which is a no-arg constructor (it doesn’t have to be public - probably can even be private (test it)).

The challenge here is that the plugin must determine that your type parameter actually corresponds directly to the type property. This is a missing feature. It should be possible to handle the two common cases:

  • Passing an item to a constructor that is a property (transitively)
  • Directly assigning a parameter to a property in the body (without any modification)

Thanks Paul. Yes, I’ve changed the code so I have primary no-arg constructors now, so I didn’t need to write a custom serializer. I had to turn my properties from vals to vars and set them in secondary constructors. It’s not ideal, but it’s still a great improvement from a few years ago when I tried Kotlin without multiplatform or kotlinx.serialization.

The library works well, but I do wish there was more documentation, specifically on custom serializers. I struggled but eventually wrote a custom serializer for a Map<String, Any?> with a few different primitive types for map values. My code is weird and some methods or properties of the descriptor return “lies”. The lies seem to have no effect with Json format but I suspect I’d run into problems with other formats.

To handle the situation in my question here, I might have to dig into generated code to see how to write a custom serializer that was part of a class hierarchy. I’m glad I could avoid that this time.