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

Serializer forClass generates class type serializer for enums

Open abrooksv opened this issue 2 years ago • 3 comments

Describe the bug When using the workaround to force generation of a plugin generated serializer for enums, the generated serializer always treats it as a class

To Reproduce

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
enum class Foo {
    HELLO,
    WORLD
}

@Serializable(with = Foo2Serializer::class)
enum class Foo2 {
    HELLO,
    WORLD
}

@Serializer(forClass = Foo2::class)
object Foo2Serializer : KSerializer<Foo2>

fun main() {
    println(Json.encodeToString(Foo.HELLO))
    println(Json.encodeToString(Foo2.HELLO))
}

Generates:

"HELLO"
{}

Expected behavior Serializer should act the same as if generated by @Serializable

Environment

  • Kotlin version: 1.9.0
  • Library version: 1.6
  • Kotlin platforms: JVM
  • Gradle version: 8.2

abrooksv avatar Aug 25 '23 16:08 abrooksv

External serializer generation is an experimental feature and likely shouldn't be allowed to be used on enums.

sandwwraith avatar Sep 12 '23 12:09 sandwwraith

That would be very sad to hear unless a new way is added to get access to a plugin generated serializer (#1169)

Right now using the generated serializer gives you support for things like case insensitive enums and @SerialName support. That is a lot of boiler plate to reproduce per enum if you want to wrap the serializer

Right now we use the plugin generated one with a fallback wrapper so that the serialization logic can be forwards compatible:

@Serializable
// Impossible to place this here to reduce copy paste from having to place at the use site: @Serializable(with = FamilySerializer::class)
enum class Family {
    @SerialName("foo")
    FOO,
    UNKNOWN
}

object FamilySerializer : KSerializer<Family> by EnumWithFallbackSerializer(Family.serializer(), Family.UNKNOWN)

class EnumWithFallbackSerializer<T : Enum<T>>(
    private val underlyingSerializer: KSerializer<T>,
    private val fallbackValue: T
) : KSerializer<T> {
    override val descriptor: SerialDescriptor = SerialDescriptor(
        "EnumWithFallbackSerializer<${underlyingSerializer.descriptor.serialName}>",
        underlyingSerializer.descriptor
    )

    override fun deserialize(decoder: Decoder): T {
        return try {
            underlyingSerializer.deserialize(decoder)
        } catch (e: SerializationException) {
            thisLogger().warn("Error deserializing message ${underlyingSerializer.descriptor.serialName}, falling back to $fallbackValue", e)
            fallbackValue
        }
    }

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

abrooksv avatar Sep 12 '23 17:09 abrooksv

So it's another use-case for #1169, I see

sandwwraith avatar Sep 13 '23 10:09 sandwwraith

Fixed by #1169

sandwwraith avatar Jul 10 '24 14:07 sandwwraith