kotlinx.serialization icon indicating copy to clipboard operation
kotlinx.serialization copied to clipboard

Custom serializer for enums

Open aleien opened this issue 4 years ago • 7 comments

Hello!

I want to implement custom serializer for enums with fallback, because currently kotlinx fails to convert enum when backend sends new type, that is not supported yet. I wanted to use StringDesriptor, but it recently gone from api ): What is correct way now to implement custom enum serializer?

aleien avatar Feb 17 '21 16:02 aleien

One way to handle that with a custom serializer would be:

@Serializable(with = DrmTypeSerializer::class)
enum class DrmType(val key: String) {
    Widevine("widevine"),
    None("none");

    companion object {
        fun findByKey(key: String, default: DrmType = DrmType.None): DrmType {
            return DrmType.values().find { it.key == key } ?: default
        }
    }

}

@Serializer(forClass = DrmType::class)
object DrmTypeSerializer : KSerializer<DrmType> {
    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("DrmType", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: DrmType) {
        encoder.encodeString(value.key)
    }

    override fun deserialize(decoder: Decoder): DrmType {
        return try {
            val key = decoder.decodeString()
            DrmType.findByKey(key)
        } catch (e: IllegalArgumentException) {
            DrmType.None
        }
    }
}

Working with 1.1.0 of serialization library.

Unfortunately this is very specific and I would like to have a more generic approach.

I found this old discussion https://github.com/Kotlin/kotlinx.serialization/issues/31, but its a bit hard to follow and it seems to be outdated because I can not find any CommonEnumSerializer .

@sandwwraith can you give me some hint how enums could be handled with default values to be forward compatible?

Thanks

wman1980 avatar May 05 '21 13:05 wman1980

@wman1980 We now have coerceInputValues key in Json that allows using default value for unknown enum element. Is this sufficient for your case? If not, custom serializer you mentioned should be sufficient. What do you mean by 'more generic'?

sandwwraith avatar May 11 '21 13:05 sandwwraith

Hm ... coerceInputValues seems to be the right one, but I could not make it work. E.g. Ihave the following enum:

@Serializable
enum class ActionType {
    Ok,
    Retry,
    Confirm,
    None
}

and a data class

@Serializable
data class Dummy(
    val actionType: ActionType = ActionType.None
)

When I do the following:

val json = Json {  }
val jsonString = "{\"actionType\":\"Foo\"}"
val actionTypeFromJsonString = json.decodeFromString<Dummy>(jsonString)

I would expect that if I set coerceInputValues to true it would fallback to the default one (ActionType.None), but instead it crashes with ActionType does not container element with name 'Foo'. What did I do wrong?

wman1980 avatar May 11 '21 14:05 wman1980

Ahhh stupid meee!

Works superb, forgot to set the flag to true in

val json = Json{ coerceInputValues = true}.

Thanks for the hint!

wman1980 avatar May 11 '21 14:05 wman1980

But if you use coerceInputValues = true then you can't have any Optional<> wrapper type over your primitive data type to check if values are there in json or not.

himanshufoodpanda avatar Dec 07 '21 09:12 himanshufoodpanda

coerceInputValues also does not work when decoding a collection of enum values, e.g. List<EnumType>. This caught us in production today.

What would be very nice is a @SerialFallback annotation or similar that we could use at the enum declaration, rather than relying on default parameters in the use sites.

tadfisher avatar Jun 23 '22 17:06 tadfisher

I found a way to do this without too much code duplication using a generic custom serializer https://stackoverflow.com/a/69438279/503402

silverhammermba avatar Sep 16 '22 16:09 silverhammermba