springdoc-openapi 如何在不更改 toString 的情况下使用 @JsonValue 枚举格式?
How can have springdoc-openapi use the @JsonValue enum format without changing toString?
我有一个 Spring 使用 springdoc-openapi to generate Swagger API documentation for my controllers. One of the enums used in the JSON request/response has a different JSON representation than its value
/toString()
. This is achieved using the Jackson @JsonValue
注释的引导应用程序:
public enum Suit {
HEARTS("Hearts"), DIAMONDS("Diamonds"), CLUBS("Clubs"), SPADES("Spades");
@JsonValue
private final String jsonValue;
Suit(String jsonValue) { this.jsonValue = 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" }
}
}
}
}
}
存在已关闭的问题 #1101 in the springdoc-openapi project which requests allowing @JsonValue
to affect the enum serialization. However, that issue was closed,因为没有提交 PR。
如何让枚举列表匹配 REST 端点的实际 JSON 类型 accepted/returned,而不是 toString()
值?
我解决这个问题的第一个想法是使用 @Schema(allowableValues = {...}]
annotation from Swagger Core。但是,无论是错误还是设计,这都会添加到值列表中,而不是替换它:
@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;
}
}
招摇URL:http://localhost:8080/v3/api-docs
一种解决方案是将 @JsonValue
实现替换为 @JsonProperty
:
public enum Suit {
@JsonProperty("Hearts") HEARTS,
@JsonProperty("Diamonds") DIAMONDS,
@JsonProperty("Clubs") CLUBS,
@JsonProperty("Spades") SPADES;
}
请注意,如果以编程方式需要该值,这确实会导致一些重复,因为需要在 @JsonProperty
和枚举值中指定它。
可以创建一个 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 String 表示形式,这意味着将在适用的地方使用 @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();
}
}
由于错误 #3998 in Swagger Core,@JsonValue
在 public 方法上处理正确,但在字段上处理不当。因此,添加 public 访问器方法将按预期工作:
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;
}
}
我有一个 Spring 使用 springdoc-openapi to generate Swagger API documentation for my controllers. One of the enums used in the JSON request/response has a different JSON representation than its value
/toString()
. This is achieved using the Jackson @JsonValue
注释的引导应用程序:
public enum Suit {
HEARTS("Hearts"), DIAMONDS("Diamonds"), CLUBS("Clubs"), SPADES("Spades");
@JsonValue
private final String jsonValue;
Suit(String jsonValue) { this.jsonValue = 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" }
}
}
}
}
}
存在已关闭的问题 #1101 in the springdoc-openapi project which requests allowing @JsonValue
to affect the enum serialization. However, that issue was closed,因为没有提交 PR。
如何让枚举列表匹配 REST 端点的实际 JSON 类型 accepted/returned,而不是 toString()
值?
我解决这个问题的第一个想法是使用 @Schema(allowableValues = {...}]
annotation from Swagger Core。但是,无论是错误还是设计,这都会添加到值列表中,而不是替换它:
@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;
}
}
招摇URL:http://localhost:8080/v3/api-docs
一种解决方案是将 @JsonValue
实现替换为 @JsonProperty
:
public enum Suit {
@JsonProperty("Hearts") HEARTS,
@JsonProperty("Diamonds") DIAMONDS,
@JsonProperty("Clubs") CLUBS,
@JsonProperty("Spades") SPADES;
}
请注意,如果以编程方式需要该值,这确实会导致一些重复,因为需要在 @JsonProperty
和枚举值中指定它。
可以创建一个 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 String 表示形式,这意味着将在适用的地方使用 @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();
}
}
由于错误 #3998 in Swagger Core,@JsonValue
在 public 方法上处理正确,但在字段上处理不当。因此,添加 public 访问器方法将按预期工作:
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;
}
}