1

I am using AttributeConvertor to convert an Enum to Short while persisting the value in database and vice-versa while reading the value from database as follows:

@Converter
class MyEnumConvertor : AttributeConverter<MyEnum, Short> {

    override fun convertToDatabaseColumn(attribute: MyEnum): Short = MyEnum.toValue(attribute)

    override fun convertToEntityAttribute(dbData: Short): MyEnum = MyEnum.fromValue(dbData)
}

enum class MyEnum(val value: Short) {
    RED(0),
    GREEN(1);

    companion object {
        private val valueToEnumMap = MyEnum.values().associateBy(MyEnum::value)
        fun fromValue(value: Short): MyEnum = valueToEnumMap[value]
            ?: throw Exception("Invalid value: $value")

        fun toValue(status: MyEnum): Short = status.value
    }
}

Usage:

@Entity
@Table(name = "my_table")
class MyEntity(

    @Id
    val id: Long,

    @Convert(converter = MyEnum::class)
    @Column(name = "status", nullable = false)
    val color: MyEnum

Now in other tables too I have many conversions like this, and it's around 10 convertors written now. I think it's not a good practice of having these many Convertors.

Any Solution on how to avoid these? Or, any genric solution where I can create only one convertor and reuse them?

PS: I know @Enumerated(Enumtype.ORDINAL) works, but if the order of enums is changed (say, RED and GREEN order) then it will break the code. So, not using this approach.

  • You have 'the' approach but are choosing not to use it. Enums are not safe for changes, as changing the order is just as much a risk as someone coming in and putting Blue right after Red and Making Green a 2. Only you can protect your app from such mistakes - generally with unit tests that ensure values of 0,1 etc map to the proper/expected Enum type when read back. As for generic solutions - make all your Enums extend some enum superclass or interface a generic converter can use to get your alternate ordinal value from. – Chris May 09 '23 at 15:00

1 Answers1

0

I think you should be able to do this broadly in the following way:

  1. Define an interface something like this: interface WithValue { val value: Short }
  2. Require each of value enums to implement this interface
  3. Create a convertor that consumes Enum
    • during the serialization look to see if the Enum implements WithValue and it if does do your special serialization, otherwise invoke the standard processing of Enums
    • during deserialization look to see if the incoming node has a value which is Short compliant (probably Number) and if it is do you special version, otherwise call the standard processing of Enums
  4. Register the convertor in a Module once and declare your ObjectMapper as a Spring bean so Spring uses it everywhere:
    @Bean
    fun makeObjectMapper(): ObjectMapper {
        return JsonMapper.builder()
            .addModule(KotlinModule.Builder().configure(KotlinFeature.StrictNullChecks, true).build())
            .addModule(MySpecialValueBasedEnumModule())
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .build()
    }

(I have to ask why bother? isn't the enum name good enough - clear for humans and efficient enough for machines to index?)

AndrewL
  • 2,034
  • 18
  • 18
  • thanks for the solution. Can you please provide a sample snippet for the 3rd point you mentioned? – KotlinOverflow May 09 '23 at 17:48
  • Someone has closed this Question as a Duplicate giving a link to a very similar question. Can you see if this is enough to get you going? – AndrewL May 10 '23 at 11:49