Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring custom converter for all Enums

Tags:

I've built many Enum classes with int getID() and MyEnum withID(int) methods that allow me to dedicate an ID to the enum values for persistence purposes (thus avoiding changes due to order/name change with regards to external storage of the enum).

I'd like to build a custom converter that does some reflection to look for these methods, and use them or back up to Ordinal/String conversions when they are not found.

Does a generic Enum converter seem possible to anyone? This is only my second foray into Converters.

like image 604
David Parks Avatar asked Mar 03 '11 09:03

David Parks


2 Answers

I'd say you are trying to solve the wrong problem. I usually persist enums as Strings and thereby avoid this problem in the first place. The drawback is of course a larger DB field, but that's hardly significant.


That said:

I'd say it's possible in general, but not in a clean way. What I'd do is let all these enums implement a common interface (either just a marker interface or one that contains the int getId() method). Now register your PropertyEditor for this interface only, then you're not breaking too much standard functionality.

Then, your next problem is that you are relying on static factory methods, which can not be done in a generic way. Of course your PropertyEditor can do:

enumClass.getDeclaredMethod("withId", int.class).invoke(id)

but I'd call that very hacky. How about something like this:

Object target = null;
for(Object e : EnumSet.allOf(yourEnumClass)){
    if(e instanceof MyInterface && ((MyInterface)e).getId()==thisId){
        target = e;
        break;
    }
}
return target;

Now you are not using any static factory methods and you have compile time safety, as long as your enums implement a common interface.


Update: with the new Converter SPI it gets easier. Use a custom ConverterFactory:

public class CustomEnumConverterFactory implements
    ConverterFactory<String, Enum<?>>{

    @Override
    public <T extends Enum<?>> Converter<String, T> getConverter(
        final Class<T> targetType){
        return WithId.class.isAssignableFrom(targetType)
            ? new EnumWithIdConverter(targetType)
            : new StandardEnumConverter(targetType);
    }

}

Register it like this:

<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <!-- converters is a set of both converters and converterfactories -->
        <bean class="foo.bar.CustomEnumConverterFactory" />
    </property>
</bean>
like image 115
Sean Patrick Floyd Avatar answered Sep 26 '22 14:09

Sean Patrick Floyd


Extended from WebMvcConfigurerAdapter

@Override
@SuppressWarnings("unchecked")
public void addFormatters(FormatterRegistry registry) {
    registry.addConverterFactory(new ConverterFactory<String, Enum>() {
        @Override
        public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
            return source -> {
                try {
                    return (T) Enum.valueOf(targetType, source);
                } catch (Exception e) {
                    return targetType.getEnumConstants()[Integer.parseInt(source)];
                }
            };
        }
    });
    super.addFormatters(registry);
}
like image 23
BaiJiFeiLong Avatar answered Sep 24 '22 14:09

BaiJiFeiLong