如何让Spring Boot适配OpenAPI生成代码中的枚举值?
Spring Boot + OpenAPI Generator 枚举参数自动转换方案
问题背景
我在Spring Boot应用中采用API优先方案,基于自定义的openapi.yaml文件生成代码。其中有一个API接口,接收必填请求参数type,该参数是自定义枚举SomeEnum的数组类型。
问题现象
通过Swagger编辑器生成的请求调用时返回400错误:
curl -X 'GET' \ 'https://localhost:8080/api/1/demo?type=best_value' \ -H 'accept: application/json'
日志报错信息:
2023-04-06 15:52:05.223 WARN 16396 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.util.List'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@javax.validation.constraints.NotNull @io.swagger.v3.oas.annotations.Parameter @javax.validation.Valid @org.springframework.web.bind.annotation.RequestParam com.ronkitay.openapidemo.model.SomeEnum] for value 'another-value'; nested exception is java.lang.IllegalArgumentException: No enum constant com.ronkitay.openapidemo.model.SomeEnum.another-value]
但使用枚举常量名(而非@JsonValue指定的字符串值)调用时可正常工作:
curl http://localhost:8080/api/1/demo?type=ANOTHER_VALUE
生成的SomeEnum代码如下:
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-04-06T15:51:49.262+03:00[Asia/Jerusalem]") public enum SomeEnum { VALUE1("value1"), ANOTHER_VALUE("another-value"), BEST_VALUE("best_value"), THEVALUE("thevalue"); private String value; SomeEnum(String value) { this.value = value; } @JsonValue public String getValue() { return value; } @Override public String toString() { return String.valueOf(value); } @JsonCreator public static SomeEnum fromValue(String value) { for (SomeEnum b : SomeEnum.values()) { if (b.value.equals(value)) { return b; } } throw new IllegalArgumentException("Unexpected value '" + value + "'"); } }
当前解决方案
我通过编写自定义转换器实现了预期的参数转换功能:
@Configuration public class Config implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(new SomeEnumFormatter()); } public static class SomeEnumFormatter implements ConverterFactory<String,SomeEnum> { @Override public <T extends SomeEnum> Converter<String, T> getConverter(Class<T> targetType) { return new StringToSomeEnumConverter<>(targetType); } public static class StringToSomeEnumConverter<T extends SomeEnum> implements Converter<String, T> { private Class<T> targetClass; public StringToSomeEnumConverter(Class<T> targetClass) { this.targetClass = targetClass; } @Override public T convert(String source) { return (T) SomeEnum.fromValue(source); } } } }
核心问题
是否存在Spring或OpenAPI Generator的内置配置,能够自动实现这种枚举参数的转换逻辑,无需为每个枚举单独编写自定义转换器?
答案
有两种更简洁的方案可以替代自定义转换器:
1. OpenAPI Generator 配置自动生成转换器
在OpenAPI Generator的配置中添加以下参数,让生成器自动为所有枚举创建Spring转换器:
- Maven插件配置:
<plugin> <groupId>org.openapitools</groupId> <artifactId>openapi-generator-maven-plugin</artifactId> <version>最新版本</version> <executions> <execution> <goals> <goal>generate</goal> </goals> <configuration> <!-- 其他配置 --> <configOptions> <useSpringConverter>true</useSpringConverter> </configOptions> </configuration> </execution> </executions> </plugin>
- Gradle插件配置:
openApiGenerate { // 其他配置 configOptions = [ useSpringConverter: "true" ] }
开启useSpringConverter后,生成器会自动为每个枚举生成对应的Converter和ConverterFactory,无需手动编写。
2. Spring全局枚举转换器
编写一个通用的枚举转换器工厂,适配所有带有@JsonCreator静态方法的枚举类,无需为每个枚举单独处理:
import java.lang.reflect.Method; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.context.annotation.Configuration; @Configuration public class GlobalEnumConverterConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(new GenericEnumConverterFactory()); } public static class GenericEnumConverterFactory implements ConverterFactory<String, Enum<?>> { @Override public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) { return new GenericEnumConverter<>(targetType); } private static class GenericEnumConverter<T extends Enum<?>> implements Converter<String, T> { private final Class<T> enumType; public GenericEnumConverter(Class<T> enumType) { this.enumType = enumType; } @Override public T convert(String source) { if (source == null || source.isEmpty()) { return null; } try { // 优先调用枚举的fromValue静态方法 Method fromValueMethod = enumType.getMethod("fromValue", String.class); return enumType.cast(fromValueMethod.invoke(null, source)); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { // 无fromValue方法时,回退到枚举常量名匹配 return Enum.valueOf(enumType, source.toUpperCase()); } } } } }
这个全局转换器会优先调用枚举类中的fromValue方法(即OpenAPI Generator生成的@JsonCreator方法),如果枚举类没有该方法,则回退到默认的枚举常量名匹配逻辑,一次性解决所有枚举的参数转换问题。
内容的提问来源于stack exchange,提问作者RonK
相关产品推荐
相关产品推荐

