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!