Serialize Enum as an Integer
Asked Answered
A

2

8

I have generated some models using the Open API tool on one of our backend APIs with Swagger. I got the following as the enum definition:

@Serializable
enum class ClientBackgroundState(val value: kotlin.Int) {

    @SerialName(value = "0")
    NONE(0),

    @SerialName(value = "1")
    FOREGROUND(1),

    @SerialName(value = "2")
    BACKGROUND(2);
}

When I use Kotlin Serializer, it serializes the above type into a String like "FOREGROUND" and the Backend API explodes because it wants an Integer.

Is there a way to configure the serializer to convert this enum to an Integer?

Audraaudras answered 30/5, 2022 at 11:37 Comment(7)
See github.com/Kotlin/kotlinx.serialization/issues/….Singhal
Why is the backend 'exploding'? Isn't it using Kotlinx Serialization to decode your enum?Radiosonde
Backend, is another team altogether, running .NET @RadiosondeAudraaudras
@Audraaudras Ah okay, thanks, I understand. Kotlinx Serialization doesn't provide this out-of-the-box, you'd have to write your own serializer and tell OpenAPI to use it. But in OpenAPI generator it looks like there's an option for it - see the test here. Or you can ask the .NET team to accept strings - that might be the easiest!Radiosonde
Can you share the relevant OpenAPI spec for ClientBackgroundState? Is type set to be integer, like this example? What version of the generator are you using?Radiosonde
Are you certain it serializes this as "FOREGROUND" and not as a string "1"? That is what the @SerialName annotation does. Of course that would still fail if the backend expects an integer. Just trying to understand whether you pinpointed the exact problem correctly; this is suspicious.Tedford
As @StevenJeuris mentioned, if I run this code it generates "1" as the json value (maybe kotlinx serialization was updated sinse this post was made?), but in any case I needed it as an int so the answer below solved the problem for meSelway
H
7

I have this solution. Create a "simple" base class:

open class EnumAsIntSerializer<T:Enum<*>>(
    serialName: String,
    val serialize: (v: T) -> Int,
    val deserialize: (v: Int) -> T
) : KSerializer<T> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(serialName, PrimitiveKind.INT)

    override fun serialize(encoder: Encoder, value: T) {
        encoder.encodeInt(serialize(value))
    }

    override fun deserialize(decoder: Decoder): T {
        val v = decoder.decodeInt()
        return deserialize(v)
    }
}

And use it this way per an enum type:

private class PartOfSpeechSerializer: EnumAsIntSerializer<PartOfSpeech>(
    "PartOfSpeech",
    { it.value },
    { v -> PartOfSpeech.values().first { it.value == v } }
)

@Serializable(with = PartOfSpeechSerializer::class)
enum class PartOfSpeech(val value: Int) {
    Undefined(0),
    Noun(1),
    Verb(2),
    Adjective(3),
    Adverb(4),
    Pronoun(5),
    Preposition(6),
    Conjunction(7),
    Interjection(8)
}
Haakon answered 5/3, 2023 at 18:53 Comment(1)
Given how the order of enums is in this example, couldn't you remove val value: Int and use the ordinal for serialization and array access (for deserialization)?Spicate
H
1

Here is a "generic" class I wrote.

Then create the 0-param constructor, e.g.

class GenderSerializer : EnumIntSerializer<Gender>(Gender.entries)

for the enum class

@Serializable(with = GenderSerializer::class)
enum class Gender {
    @SerialName("0")
    unknown,
    @SerialName("1")
    female,
    @SerialName("2")
    male
}
Hidrosis answered 25/9, 2024 at 12:46 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.