springdoc-openapi 如何在不更改 toString 的情况下使用 @JsonValue 枚举格式?
Posted
技术标签:
【中文标题】springdoc-openapi 如何在不更改 toString 的情况下使用 @JsonValue 枚举格式?【英文标题】:How can have springdoc-openapi use the @JsonValue enum format without changing toString? 【发布时间】:2021-10-15 05:04:51 【问题描述】:我有一个 Spring Boot 应用程序,它使用 springdoc-openapi 为我的控制器生成 Swagger API 文档。 JSON 请求/响应中使用的枚举之一具有与其 value
/toString()
不同的 JSON 表示。这是使用 Jackson @JsonValue
注释实现的:
public enum Suit
HEARTS("Hearts"), DIAMONDS("Diamonds"), CLUBS("Clubs"), SPADES("Spades");
@JsonValue
private final String jsonValue;
Suit(String jsonValue) this.jsonValue = jsonValue;
但是,在列出枚举值时,生成的 Swagger API 文档使用枚举值(特别是 toString()
的值)而不是 JSON 表示(根据 @JsonValue
):
"openapi": "3.0.1",
"info": "title": "OpenAPI definition", "version": "v0" ,
"servers": [
"url": "http://localhost:8080", "description": "Generated server url"
],
"paths": ... ,
"components":
"schemas":
"PlayingCard":
"type": "object",
"properties":
"suit":
"type": "string",
"enum": [ "Hearts", "Diamonds", "Clubs", "Spades" ]
,
"value": "type": "integer", "format": "int32"
springdoc-openapi 项目中有一个关闭的问题#1101 请求允许@JsonValue
影响枚举序列化。但是,该问题是 closed,因为没有提交任何 PR。
如何获取枚举列表以匹配 REST 端点接受/返回的实际 JSON 类型,而不是 toString()
值?
我解决这个问题的第一个想法是使用来自Swagger Core 的@Schema(allowableValues = ...]
注释。但是,无论是由于错误还是设计,这都会添加到值列表中,而不是替换它:
@Schema(allowableValues = "Hearts", "Diamonds", "Clubs", "Spades")
public enum Suit
HEARTS("Hearts"), DIAMONDS("Diamonds"), CLUBS("Clubs"), SPADES("Spades");
// ...
"suit":
"type": "string",
"enum": [
"HEARTS",
"DIAMONDS",
"CLUBS",
"SPADES",
"Hearts",
"Diamonds",
"Clubs",
"Spades"
]
可重现的示例
plugins
id 'org.springframework.boot' version '2.5.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
sourceCompatibility = '11'
repositories
mavenCentral()
dependencies
implementation 'io.swagger.core.v3:swagger-annotations:2.1.10'
implementation 'org.springdoc:springdoc-openapi-ui:1.5.10'
implementation 'org.springframework.boot:spring-boot-starter-web'
package com.example.springdoc;
import com.fasterxml.jackson.annotation.JsonValue;
public class PlayingCard
private Suit suit;
private Integer value;
public Suit getSuit() return suit;
public void setSuit(Suit suit) this.suit = suit;
public Integer getValue() return value;
public void setValue(Integer value) this.value = value;
public enum Suit
HEARTS("Hearts"), DIAMONDS("Diamonds"), CLUBS("Clubs"), SPADES("Spades");
@JsonValue
private final String jsonValue;
Suit(String jsonValue) this.jsonValue = jsonValue;
package com.example.springdoc;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/playingCard")
public class PlayingCardController
@PostMapping
public PlayingCard echo(@RequestBody PlayingCard card)
return card;
招摇网址:http://localhost:8080/v3/api-docs
【问题讨论】:
有JsonProperty、JsonCreator 和覆盖toString()
的选项。你能检查一下这些是否有效吗?
JsonProperty
和(正如问题中提到的)toString()
工作。 JsonCreator
没有这样的运气(尽管它从一个值创建一个枚举并且没有以声明方式指定一个映射,但我认为它真的没有任何合理的方式可以工作)。跨度>
@aksappy 谢谢;我已将 JsonProperty
建议扩展为答案。
【参考方案1】:
一种解决方案是将@JsonValue
实现替换为@JsonProperty
:
public enum Suit
@JsonProperty("Hearts") HEARTS,
@JsonProperty("Diamonds") DIAMONDS,
@JsonProperty("Clubs") CLUBS,
@JsonProperty("Spades") SPADES;
请注意,如果以编程方式需要该值,这确实会导致一些重复,因为它需要在 @JsonProperty
以及枚举中指定一个值。
【讨论】:
【参考方案2】:可以创建一个PropertyCustomizer
Spring bean 来自定义属性。这可以针对特定枚举类型执行,也可以针对所有枚举全局执行。
具有显式列表的特定类型定制器
以下定制器将为特定枚举类型显式设置枚举值:
import com.fasterxml.jackson.databind.JavaType;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import org.springdoc.core.customizers.PropertyCustomizer;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class SuitPropertyCustomizer implements PropertyCustomizer
@Override
public Schema customize(Schema property, AnnotatedType type)
if (property instanceof StringSchema && isSuit(type))
property.setEnum(List.of("Hearts", "Diamonds", "Clubs", "Spades"));
return property;
private boolean isSuit(AnnotatedType type)
return type.getType() instanceof JavaType && ((JavaType) type.getType()).isTypeOrSubTypeOf(Suit.class);
使用@JsonValue
的全局枚举定制器
以下定制器将对所有枚举类型使用 Jackson 字符串表示,这意味着将在适用的情况下使用 @JsonValue
注释。
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import org.springdoc.core.customizers.PropertyCustomizer;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.stream.Collectors;
@Component
public class EnumValuePropertyCustomizer implements PropertyCustomizer
@Override
public Schema customize(Schema property, AnnotatedType type)
if (property instanceof StringSchema && isEnumType(type))
ObjectMapper objectMapper = Json.mapper();
property.setEnum(Arrays.stream(((JavaType) type.getType()).getRawClass().getEnumConstants())
.map(e -> objectMapper.convertValue(e, String.class))
.collect(Collectors.toList()));
return property;
private boolean isEnumType(AnnotatedType type)
return type.getType() instanceof JavaType && ((JavaType) type.getType()).isEnumType();
【讨论】:
【参考方案3】:由于Swagger Core 中的错误#3998,@JsonValue
在公共方法上可以正确处理,但在字段上则不能。因此,添加公共访问器方法将按需要工作:
public enum Suit
HEARTS("Hearts"), DIAMONDS("Diamonds"), CLUBS("Clubs"), SPADES("Spades");
private final String jsonValue;
Suit(String jsonValue) this.jsonValue = jsonValue;
@JsonValue
public String getJsonValue()
return jsonValue;
【讨论】:
以上是关于springdoc-openapi 如何在不更改 toString 的情况下使用 @JsonValue 枚举格式?的主要内容,如果未能解决你的问题,请参考以下文章
springdoc-openapi swagger-ui 中的 CSRF 支持
springdoc-openapi 在没有服务器的情况下生成 openapi yaml
使用 springdoc-openapi 和 spring-boot-starter-data-mongodb 生成 OpenAPI 文档