Схема объявления Swagger = @Schema(implementation = Map.class) представляет схему как строку в swagger-ui

Я пытаюсь создать springdoc документацию swagger, и я хотел бы представить тело запроса с типом данных Map<String, Object>в более удобочитаемом для клиентов виде. Но когда я заявляю @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(schema = @Schema(implementation = Map.class) Схема идет как String(приложил скриншот ниже)

Объявление метода

               @Operation(security = {@SecurityRequirement(name = "bearer-key")}, summary = "Create Data", operationId = "createData", description = "Create createData for the **`type`**. ")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "201", description = "Data created", content = @Content(schema = @Schema(implementation = Map.class),
                    examples = {@ExampleObject(value = "{\n" +
                            "    \"id\": \"927d810c-3ac5-4584-ba58-7c11befabf54\",\n" +
                            "}")})),
            @ApiResponse(responseCode = "400", description = "BAD Request")})
    @PostMapping(value = "/data/type", produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_JSON_VALUE)
    @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(schema = @Schema(implementation = Map.class),
            examples = {@ExampleObject(value = "{\n" +
                    "            \"label\":\"tourism\",\n" +
                    "            \"location\":\"France\"\n" +
                    "         }")}))
    ResponseEntity<Map<String, Object>> createData(@Parameter(name = "type", required = true) @PathVariable("type") String type, @Parameter(name = "request payload") @Valid @RequestBody Map<String, Object> body);

Хотя загрузка Spring автоматически определяет тип на основе сигнатуры метода, тип данных неясен. Map. Например, по умолчанию тип Map будет выведен, как показано ниже.

Но я хотел бы показать схему в более понятной форме для клиента, который обращается к моему API. Я видел, что в Github есть закрытая заявка без надлежащего решения. Согласно моему требованию, тело запроса должно быть независимым от типа и динамической парой ключ-значение, поэтому нет другого пути, кроме получения запроса как Map<String, Object>. Кто-нибудь реализовал лучший способ с типом Map вместо того, чтобы создавать индивидуальную модель запроса / ответа?

6 ответов

Поделившись своим подходом к решению этой проблемы, я нашел обходной путь для @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(schema = @Schema(implementation = Map.class) Схема появляется как проблема со строкой.

Я объявил настраиваемую схему под названием Map в объявлении bean-компонента OpenAPI, как показано ниже.

new OpenAPI()
                .components(new Components()
                        .addSchemas("Map", new Schema<Map<String, Object>>().addProperties("< * >", new ObjectSchema())
                        ))
                    .....
                    .....

и использовал указанную выше схему в объявлении схемы, как показано ниже

 @io.swagger.v3.oas.annotations.parameters.RequestBody(
            content = @Content(mediaType = APPLICATION_JSON_VALUE, 
                 schema = @Schema(ref = "#/components/schemas/Map"))

Вышеупомянутый подход можно использовать вместо ApiResponse также как ниже

 @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200",
            content = @Content(mediaType = APPLICATION_JSON_VALUE, 
                 schema = @Schema(ref = "#/components/schemas/Map"))

Примечание. Если мы используем описанный выше подход с настраиваемой схемой, нам не нужно изменять или игнорировать какие-либо типы, которые SpringDoc используется внутри.

У меня одна конечная точка API, тело запроса ожидает HashMap. Информации о том, как исправить проблему «Пример значения», не так много. Ответ Прасанта привел меня в нужное место. Я публикую свое решение для полноты картины, но вся заслуга принадлежит ему. (PS: я пытался проголосовать за его ответ, но у меня недостаточно «очков»)

Сторона конфигурации:

      @Configuration
@OpenAPIDefinition
public class DocsConfiguration {
    @Bean
    public OpenAPI customOpenAPI() {
        Schema newUserSchema = new Schema<Map<String, Object>>()
                .addProperties("name",new StringSchema().example("John123"))
                .addProperties("password",new StringSchema().example("P4SSW0RD"))
                .addProperties("image",new StringSchema().example("https://robohash.org/John123.png"));

        return new OpenAPI()
                //.servers(servers)
                .info(new Info()
                        .title("Your app title")
                        .description("App description")
                        .version("1.0")
                        .license(new License().name("GNU/GPL").url("https://www.gnu.org/licenses/gpl-3.0.html"))
                )
                .components(new Components()
                        .addSchemas("NewUserBody" , newUserSchema)
                );
    }
}

Сторона контроллера:

          @io.swagger.v3.oas.annotations.parameters.RequestBody(
            content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
                    schema = @Schema(ref = "#/components/schemas/NewUserBody")))
    @PostMapping("/v1/users")
    public Response<User> upsertUser(@RequestBody HashMap<String,Object> user) {
         //Your code here
    }

Я хотел бы обновить ответ Родири для моей ситуации. Мне пришлось объединить ответ Родири и этот ответ Ондржея Чернобилы на SO-вопрос SpringDoc — Как программно добавлять схемы . Я использую java 11, spring-boot-starter-parent 2.5.6 и springdoc-openapi-ui 1.5.12, который, как мне кажется, использует swagger 3.52.5.

      <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.5.12</version>
</dependency>

Мой конфиг

      import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.StringSchema;
import org.springdoc.core.customizers.OpenApiCustomiser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@OpenAPIDefinition
public class DocsConfiguration {

  @Bean
  public OpenApiCustomiser openApiCustomiser() {
    return openApi -> {
      var NewUserBodySchema = new ObjectSchema()
          .name("NewUserBody")
          .title("NewUserBody")
          .description("Object description")
          .addProperties("name", new StringSchema().example("John123"))
          .addProperties("password", new StringSchema().example("P4SSW0RD"))
          .addProperties("image", new StringSchema().example("https://robohash.org/John123.png"));

      var schemas = openApi.getComponents().getSchemas();
      schemas.put(NewUserBodySchema.getName(), NewUserBodySchema);
    };
  }

}

Для моей конечной точки я использую получение, которое возвращает карту, поэтому она отличается от принятого ответа.

      @GetMapping(value = "/{userId}")
@Operation(
    summary = "Get Something",
    description = "Some desciption",
    responses = {
        @ApiResponse(
            responseCode = "200",
            description = "The Map Response",
            content = {
                @Content(
                    mediaType = MediaType.APPLICATION_JSON_VALUE,
                    schema = @Schema(ref = "#/components/schemas/NewUserBody")
                )
            })
    }
)
public ResponseEntity<Map<String, Object>> getMap(@PathVariable String userId) 

Это стандартное поведение библиотеки springdoc-openapi, чтобы игнорировать другие вводимые параметры, поддерживаемые Spring MVC.

Если вы хотите изменить это поведение, вы можете просто исключить его следующим образом:

    SpringDocUtils.getConfig().removeRequestWrapperToIgnore(Map.class);

Сам сегодня столкнулся с этой проблемой. Как оказалось, на самом деле это проблема дизайна Swagger (@см . связанный вопрос ).

Тем не менее, я тоже попробовал свои силы, используя подходы отсюда и из другой темы.

Вот мой OpenAPI с одной настраиваемой схемой для Map<Integer,String>:

      @Configuration
@OpenAPIDefinition(
        info = @io.swagger.v3.oas.annotations.info.Info(
                title = "ACME Inc. REST API",
                version = "1.0",
                description = "This is an overview of all REST endpoints of this application",
                contact = @io.swagger.v3.oas.annotations.info.Contact(name = "John Doe", url = "https://acme-inc.com/", email = "john.doe@acme-inc.com")
        )
)
public class OpenAPIConfig {
        public static final String ERROR_CODE_MAPPER = "ErrorCode-Mapper";

        @Bean
        public OpenApiCustomiser openApiCustomiser() {
                return openApi -> {
                        Components components = openApi.getComponents();
                        for(Schema<?> schema: buildCustomSchemas()) {
                                components.addSchemas(schema.getName(), schema);
                        }
                };
        }

        private static List<Schema<?>> buildCustomSchemas() {
                ArrayList<Schema<?>> result = new ArrayList<>();

                Schema<?> integerStringMap = new Schema<Map<Integer, String>>()
                        .name(ERROR_CODE_MAPPER)
                        .type("object")
                        .addProperty("error code", new StringSchema().example("Error message belonging to the error code")).example(getErrorCodeExample());
                result.add(integerStringMap);
                // Build more custom schemas...

                return result;
        }

        private static Map<Integer, String> getErrorCodeExample() {
                Map<Integer, String> example = new HashMap<>();
                example.put(666, "Oh no..., the devil himself  showed up and stopped your request");
                return example;
        }
}

( ПРИМЕЧАНИЕ. Найдите исходный код swagger io.swagger.v3.oas.models.mediaдля полезных служебных классов, таких как StringSchema. Вы не должны писать все с нуля.)

И это моя конечная точка REST:

      @Operation(summary = "This endpoint returns a list of system error codes, that can occur during processing requests.")
@ApiResponses(value = {
        @ApiResponse(
                responseCode = "200",
                description = "Map of all system error codes mapping to their messages",
                content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(ref = "#/components/schemas/"+ ERROR_CODE_MAPPER))}
        )
})
@GetMapping("/error-codes")
public Map<Integer, String> listErrorCodes() {
    // return your map here...
}

Это производит что-то вроде этого:

Важно знать, что в объекте JSON ключ всегда имеет тип string. Таким образом, тип не должен быть указан явно. Имея это в виду, это схема:

  1. Я создал класс расширения HashMap:

             @Schema(description = "Response-Object Map<String, EcoBalance).")
     public class EcoMap extends HashMap<String, EcoBalance> {
       @JsonIgnore
       @Override
       public boolean isEmpty() {
         return super.isEmpty();
       }
     }
    
  2. использовать его как объект ответа

             @ApiResponse(responseCode = "200", content = @Content(mediaType = .., schema = @Schema(implementation = EcoMap.class)), headers = ..
    
  3. имейте в виду, что генератор OpenAPI 3 не генерирует такую ​​клиентскую модель, но правильно ссылается на openapi.yml (и даже проверяет).

Другие вопросы по тегам