【问题标题】:How can have springdoc-openapi use the @JsonValue enum format without changing toString?springdoc-openapi 如何在不更改 toString 的情况下使用 @JsonValue 枚举格式?
【发布时间】: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

【问题讨论】:

  • JsonPropertyJsonCreator 和覆盖toString() 的选项。你能检查一下这些是否有效吗?
  • JsonProperty 和(正如问题中提到的)toString() 工作。 JsonCreator 没有这样的运气(尽管它从一个值创建一个枚举并且没有以声明方式指定一个映射,但我认为它真的没有任何合理的方式可以工作)。跨度>
  • @aksappy 谢谢;我已将 JsonProperty 建议扩展为答案。

标签: java jackson swagger springdoc springdoc-openapi-ui


【解决方案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;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-10-18
        • 1970-01-01
        • 2015-11-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-10
        • 2019-02-01
        相关资源
        最近更新 更多