Serializable delegates

If I have a class with delegated properties, is there a way to make those properties serializable?

Well, I decided to post my findings in case this helps anybody else. If there’s a better way to do this I’d love some input because this ended up being a bit more work than I was expecting.

I have Style objects that use property delegates in order to separate calculated and explicit values. I want these style objects to be serializable where the values that were explicitly set are serialized.

All styles have a base class. That base class has a list of the properties that were provided via a style property delegate.

In my base Style class

abstract class StyleBase

I have a way to provide a property delegate that unsafely provides the serializer:

// NB: Property is skipped if it's not serializable
protected inline fun <reified P : Any> prop(defaultValue: P) = 
  StyleProp(defaultValue, P::class.serializerOrNull()) 

(I’d prefer this to be safe, but to have every single property provide its serializer manually would be very tedious.)

For the style property delegate, I keep a reference to the serializer and properties for the calculated and explicit values.

class StyleProp<T>(
		var defaultValue: T,
		val serializer: KSerializer<T>?
) : ReadWriteProperty<Style, T>
// Properties for calculatedValue, explicitValue...

Then for the serializer. This is where things felt pretty gnarly and it took a lot of code and digging through serialization source code to figure out how to solve:

class StyleSerializer<T : StyleBase>(name: String, val factory: () -> T) : KSerializer<T> {

	private val default = factory()

	private val serializableProps = default.allProps.filter { != null && it.serializer != null }

	override val descriptor = object : SerialDescriptor {

		override val elementsCount: Int
			get() = serializableProps.size

		override val kind: SerialKind = StructureKind.OBJECT

		override val serialName: String = name

		override fun getElementAnnotations(index: Int): List<Annotation> {
			if (!serializableProps.rangeCheck(index)) throw IndexOutOfBoundsException()
			return emptyList()

		override fun getElementDescriptor(index: Int): SerialDescriptor {
			return serializableProps[index].serializer!!.descriptor

		override fun getElementIndex(name: String): Int {
			return serializableProps.indexOfFirst { == name }

		override fun getElementName(index: Int): String {
			return serializableProps[index].name!!

		override fun isElementOptional(index: Int): Boolean {
			if (!serializableProps.rangeCheck(index)) throw IndexOutOfBoundsException()
			return true

	override fun deserialize(decoder: Decoder): T {
		val compositeDecoder = decoder.beginStructure(descriptor)
		val obj = factory()
		while (true) {
			val index = compositeDecoder.decodeElementIndex(descriptor)
			if (index == CompositeDecoder.READ_DONE) break

			if (!serializableProps.rangeCheck(index)) error("Unexpected index $index")
			val sProp = serializableProps[index]
			val prop = obj.allProps.first { == }

			val value = compositeDecoder.decodeSerializableElement(descriptor, index, prop.serializer!!)
			prop.explicitValue = value

		return obj

	override fun serialize(encoder: Encoder, value: T) {
		val descriptor = descriptor
		val composite = encoder.beginStructure(descriptor)
		for (i in 0..serializableProps.lastIndex) {
			val sProp = serializableProps[i]
			val prop = value.allProps.first { == }
			if (prop.explicitIsSet) {

 * Adds a contextual and polymorphic style serializer to the builder.
inline fun <reified T : StyleBase> SerializersModuleBuilder.styleSerializer(packageName: String, noinline factory: () -> T) {
	// JS doesn't support class.qualifiedName yet.
	val serializer = StyleSerializer(packageName + "." + T::class.simpleName!!, factory)
	contextual(T::class, serializer)
	polymorphic(StyleBase::class) {
		T::class with serializer

val stylesModule = SerializersModule {
	val packageName = "com.acornui.component"
	styleSerializer(packageName) { BoxStyle() }
	styleSerializer(packageName) { CharStyle() }

So yeah… I’m betting that I figured this out in the most horribly complicated way possible and someone out there will educate me on on the easy way to do this :slight_smile:

This seems to boil down to that I have a map of property values and their corresponding serializers, but it took about one hundred lines of code to write a custom serializer for them.

1 Like