4

I have a DTO that contains other DTOs and a list of multipart files. I am trying to process that DTO but I can't seem to be able to read the requst.

class TeacherDTO {
   private SpecializationDto specializationDto;
   private List<MultipartFile> files;
}

@PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE},
            produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Object> saveNewTeacher(@ModelAttribute @Valid TeacherDTO teacherDto){

//process request

}

When creating an example request from Swagger UI, I get the following exception:

type 'java.lang.String' to required type 'SpecializationDto' for property 'specializationDto': no matching editors or conversion strategy found

If I put @RequestBody instead of @ModelAttribute then I get

Content type 'multipart/form-data;boundary=----WebKitFormBoundaryVEgYwEbpl1bAOjAs;charset=UTF-8' not supported]

Swagger dependencies:

<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-ui</artifactId>
   <version>1.5.2</version>
</dependency>
<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-data-rest</artifactId>
   <version>1.5.2</version>
</dependency>

OpenAPI3.0 config:



@Configuration
public class OpenApi30Config {

  private final String moduleName;
  private final String apiVersion;

  public OpenApi30Config(
      @Value("${spring.application.name}") String moduleName,
      @Value("${api.version}") String apiVersion) {
    this.moduleName = moduleName;
    this.apiVersion = apiVersion;
  }

  @Bean
  public OpenAPI customOpenAPI() {
    final var securitySchemeName = "bearerAuth";
    final var apiTitle = String.format("%s API", StringUtils.capitalize(moduleName));
    return new OpenAPI()
        .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
        .components(
            new Components()
                .addSecuritySchemes(securitySchemeName,
                    new SecurityScheme()
                        .name(securitySchemeName)
                        .type(SecurityScheme.Type.HTTP)
                        .scheme("bearer")
                        .bearerFormat("JWT")
                )
        )
        .info(new Info().title(apiTitle).version(apiVersion));
  }
}
2dor
  • 851
  • 3
  • 15
  • 35
  • Are you facing this problem only from the swagger client? – b.s Dec 17 '21 at 14:22
  • Yes, I want to get it working with the swagger UI – 2dor Dec 19 '21 at 12:16
  • You need to provide more details including swagger version etc otherwise it would be hard to depict the issue if any. – b.s Dec 19 '21 at 13:05
  • @harry updated with open api configuration. I use Open API 3.0 – 2dor Dec 19 '21 at 13:24
  • 1
    You're mixing two things here. Posting a JSON object and posting multipart data. I would recommend not doing this. Post only multipart data to some endpoint – Bart Dec 29 '21 at 16:12

1 Answers1

3

This seems to be an issue with how the springdoc-openapi-ui builds the form-data request. I was able to reproduce this and noticed that it sends a multipart-request like (intercepted through browser's dev-tools):

-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="specializationDto"\r\n\r\n{\r\n  "something": "someValue"\r\n}


-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="files"; filename="somefile.txt"
Content-Type: application/octet-stream

<content>

-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="files"; filename="somefile.txt"
Content-Type: application/octet-stream

<content>

With that payload Spring is not able to deserialize the specializationDto, resulting in the "no matching editors or conversion strategy found" exception that you've observed. However, if you send the request through postman or curl with (note the dot-notation for the specializationDto object)

curl --location --request POST 'http://localhost:8080/upload' \
--form 'files=@"/path/to/somefile"' \
--form 'files=@"/path/to/somefile"' \
--form 'specializationDto.something="someValue"'

then Spring is able to parse it correctly. Here's my rest-mapping that will log the following as expected:

    @RequestMapping(value = "/upload", method = RequestMethod.POST, 
                    consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public void upload(@ModelAttribute TeacherDto requestDto) {
        System.out.println(requestDto);
    }


// logs:
TeacherDto(specializationDto=SpecializationDto(something=someValue), files=[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@78186ea6, org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@461c9cbc])

I suggest you open a bug on their github page.

EDIT:

After OP opened a github ticket, here's part of the author's feedback:

[...] With spring, you can use @RequestPart spring annotation to describe the different parts, with the related encoding media type. Note that there is a limitation with the current swagger-ui implementation as the encoding attribute is not respected on the request.[...]

They also provided a possible workaround, which looks like this:

@PostMapping(consumes =  MediaType.MULTIPART_FORM_DATA_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> saveNewTeacher( @RequestPart(value = "specializationDto") @Parameter(schema =@Schema(type = "string", format = "binary"))  final SpecializationDto specializationDto,
        @RequestPart(value = "files")  final List<MultipartFile> files){
    return null;
}
eol
  • 23,236
  • 5
  • 46
  • 64
  • 2
    Thank you for this response. I've created a bug on their github repo and their reply is here. Can you please edit your response with their response so I can approve your message as the correct answer, given the fact that you pushed it to the right direction? https://github.com/springdoc/springdoc-openapi/issues/1416 – 2dor Jan 01 '22 at 19:34
  • Done :) Nice to hear that there's at least a workaround. – eol Jan 01 '22 at 21:09