1

I have the following sealed interface (simplified example):

sealed interface Validation { val result: Int }

...which is implemented by several enums - each one for a particular application. For example:

@Serializable(with = BoxedSerializer) // one of my attempts, see below
enum class AccountValidation(override val result: Int): Validation {
  UNKNOWN(10),
  BLOCKED(20),
  OK(30)
}

@Serializable(with = BoxedSerializer) // one of my attempts, see below
enum class PasswordValidation(override val result: Int): Validation {
  SHORT(10),
  WEAK(20),
  OK(30)
}

I want to use this enum polymorphically in serializable API types, like so:

@Serializable
data class ValidationResult(
  val userName: String,
  val validation: Validation
)

I am using Kotlin 1.7.21 with kotlin serialization 1.4.1 on the JVM. Out-of-the-box, this setup does not work, because enums are serialized as primitive types, without a type field for polymorphic serialization. I tried several other attempts:

  • Serialization via a surrogate class, as described in the official documentation, does not seem to work for enums - I end up with a stack overflow, probably from an erroneous recursion.
  • Custom serialization as described here leads to an internal compiler error.
  • Surrogate serialization in a generic wrapper class (see example code below): This also fails with an internal compiler error.
@Serializable
@SerialName("Validation")
data class ValidationBox<T : Validation>(val code: T)

class BoxedSerializer<T : Validation>(private val validationSerializer: KSerializer<T>) : KSerializer<T> {
    private val boxSerializer = ValidationBox.serializer(validationSerializer)
    override val descriptor: SerialDescriptor = boxSerializer.descriptor

    override fun serialize(encoder: Encoder, value: T) {
        val boxed = ValidationBox(value)
        encoder.encodeSerializableValue(boxSerializer, boxed)
    }

    override fun deserialize(decoder: Decoder): T {
        val boxed: ValidationBox<T> = decoder.decodeSerializableValue(boxSerializer)
        return boxed.code
    }
}

@Test
fun `polymorphically serialize and deserialize`() {
    val validation: Validation = AccountValidation.BLOCKED
    val validationJson = Json.encodeToString(validation)
    val validationDeserialized = Json.decodeFromString<Validation>(validationJson)
    assertEquals(validation, validationDeserialized)
}
   

What I would like to get as output (JSON example):

{
  "userName": "myUserName",
  "validation": {"PasswordValidation": "WEAK"}
}

or (closer to the standard)

{
  "userName": "myUserName",
  "validation": {
      "type": "PasswordValidation",
      "value": "WEAK"
    }
}

How would a semi-custom or (if necessary) full-custom serializer look like?

Thanks for your help!

Ulrich Schuster
  • 1,670
  • 15
  • 24
  • Looking at the example JSON, `validation` is an object. If you want to encode/decode an enum as a JSON object instead of a string then you either need to create a custom KSerializer, or define a data class that matches the JSON (wrap the enum in a data class). If you update your question to include your attempts, it will be easier to help. – aSemy Dec 16 '22 at 15:32
  • Thanks @aSemy. I added an example using a generic surrogate class. This results in an internal compiler error. – Ulrich Schuster Dec 16 '22 at 16:15
  • Can you provide a complete example? `BoxedSerializer` is defined, but not used. – aSemy Dec 17 '22 at 14:22
  • Ah, sorry - that was my editing. I use `BoxedSerializer` for the enums (added the annotation now) – Ulrich Schuster Dec 18 '22 at 07:28

0 Answers0