Vojtěch
Vojtěch

Reputation: 12416

Spring MVC enum recognition

Within our controllers we want use enums as method parameters:

@GetMapping("/feed")
public void feed(@RequestParam myEnum: MyEnum)

This works until we try to use strings which doesn't match by case. For that I found a similar question:

This however requires setting handler for each enum. I would rather like to implement my custom handler for all enums. Something like:

val ordinal = param.toString().toIntOrNull()
// Reflection on built-in Kotlin types is not yet fully supported... so ...
return if (null == ordinal)
    try {
        type.jvmErasure.java.methods.find { it.name == "valueOf" }!!.invoke(null, param.toString())
    } catch (e: Exception) {
        type.jvmErasure.java.methods.find { it.name == "valueOf" }!!.invoke(null, param.toString().toUpperCase())
    }
else
    type.jvmErasure.java.enumConstants[ordinal]

Sorry this is written in Kotlin, but shouldn't be hard to understand. It basically tries valueOf on the original string, then on uppercase version and if it is number, it uses ordinal value of the enum.

I am looking for a general solution, not handler for single enum.

Upvotes: 0

Views: 1246

Answers (1)

Denis Zavedeev
Denis Zavedeev

Reputation: 8297

Inspired by StringToEnumConverterFactory.

I will use Java to give you a sample solution.

Following code uppercases strings are to be converted to Enum and then invokes valueOf, but you can put any logic to the convert method.

@SuppressWarnings({"unchecked", "rawtypes"})
public class CaseInsensitiveStringToEnumConverterFactory implements ConverterFactory<String, Enum> {
  @Override
  public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {

    return new CaseInsensitiveStringToEnumConverter(getEnumType(targetType));
  }

  // ConversionUtils are package private. The method is copy-pasted
  private static Class<?> getEnumType(Class<?> targetType) {
    Class<?> enumType = targetType;
    while (enumType != null && !enumType.isEnum()) {
      enumType = enumType.getSuperclass();
    }
    Assert.notNull(enumType, () -> "The target type " + targetType.getName() + " does not refer to an enum");
    return enumType;
  }

  @SuppressWarnings("unchecked")
  private class CaseInsensitiveStringToEnumConverter<T extends Enum> implements Converter<String, T> {
    private final Class<T> enumClass;

    CaseInsensitiveStringToEnumConverter(Class<T> enumClass) {
      this.enumClass = enumClass;
    }

    @Override
    public T convert(String source) {
      if (source.isEmpty()) {
        return null;
      }
      source = source.toUpperCase().trim();
      return (T)Enum.valueOf(enumClass, source);
    }
  }
}

MvcConfig:

@Configuration
public class MvcConfig implements WebMvcConfigurer {

  @Override
  public void addFormatters(FormatterRegistry registry) {
    registry.addConverterFactory(new CaseInsensitiveStringToEnumConverterFactory());
  }
}

Sample enum:

public enum Color {
  RED,
  BLUE,
  GREEN
}

SampleContoller:

@RestController
public class MyController {
  @RequestMapping("/")
  public Map<String, Object> testMethod(@RequestParam("color") Color color) {
    Map<String, Object> map = new HashMap<>();
    map.put("color", color);
    return map;
  }
}

Testing:

$ curl 'localhost:8080/?color=red'

Output:

{"color":"RED"}

Upvotes: 1

Related Questions