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));
}
}
CodePudding user response:
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.
CodePudding user response:
Have you tried to use @RequestMapping instead of @PostMapping ? Something like this:
@RequestMapping(value = "/ex/foos", method = RequestMethod.POST)
public ResponseEntity<Object> saveNewTeacher(@Valid @RequestBody TeacherDTO teacherDTO) throws Exception {
//process instance
}
class TeacherDTO {
@Valid
private SpecializationDto specializationDto;
private List<MultipartFile> files;
}
//supposing your nested DTO is something like that
class SpecializationDto {
@NotNull
@NotEmpty
String something;
}