You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决@WebMvcTest中DTO含Map元数据的文件上传转换异常?

问题描述

我想要实现一个支持上传带元数据Map的文件接口,把参数封装到ConvertFileRequest这个POJO里。但在@WebMvcTest测试中,将metadata作为JSON字符串传入请求参数时,报错no matching editors or conversion strategy found。想明确:

  • 是@WebMvcTest未配置相关转换器?
  • 还是MockMvc的使用方式有误?
  • 或是设计上存在遗漏?
  • 有没有无需JSON序列化就能把剩余参数存入metadata字段的方法?

相关测试代码

@ActiveProfiles("test")
@WebMvcTest(controllers = FileFormatConvertController.class)
class FileFormatConvertControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    void shouldUploadFileWithMetadata() throws Exception {
        MockMultipartFile file = new MockMultipartFile(
                "file",
                "file.docx",
                MediaType.APPLICATION_OCTET_STREAM_VALUE,
                "file content".getBytes()
        );
        mockMvc.perform(multipart("/file")
                                .file(file)
                                .param("title", "File title")
                                .param("metadata", new ObjectMapper().writeValueAsString(Map.of("some-value", "some-key")))
                )
                .andExpect(status().isOk());
    }
}

测试报错信息

Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'convertFileRequest' on field 'metadata': rejected value [{"some-value":"some-key"}]; codes [typeMismatch.convertFileRequest.metadata,typeMismatch.metadata,typeMismatch.java.util.Map,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [convertFileRequest.metadata,metadata]; arguments []; default message [metadata]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Map' for property 'metadata'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map' for property 'metadata': no matching editors or conversion strategy found]]

其余核心代码

控制器类:

@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
class FileFormatConvertController {

    @PostMapping
    ResponseEntity<ConvertFileResponse> convertFile(@Valid @ModelAttribute final ConvertFileRequest request) {
       return ResponseEntity.ok().build();
    }
}

请求POJO:

@Data
public class ConvertFileRequest {
    @NotNull
    private MultipartFile file;
    @NotEmpty
    private String title;
    private Map<String, String> metadata;
}

原因与解决方案

核心原因

报错本质是Spring MVC默认没有提供String(JSON字符串)到Map类型的属性转换器,@ModelAttribute绑定参数时无法自动完成这个转换,和@WebMvcTest本身无关,属于默认配置缺失对应转换规则。

解决方案

方案一:添加自定义转换器,支持JSON字符串转Map

创建一个实现Converter<String, Map<String, String>>的转换器,让Spring能识别JSON字符串并转换为Map:

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
public class StringToMapConverter implements Converter<String, Map<String, String>> {

    private final ObjectMapper objectMapper;

    public StringToMapConverter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public Map<String, String> convert(String source) {
        try {
            return objectMapper.readValue(source, new TypeReference<>() {});
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid JSON format for metadata", e);
        }
    }
}

在测试类中引入该转换器:

@ActiveProfiles("test")
@WebMvcTest(controllers = FileFormatConvertController.class)
@Import(StringToMapConverter.class) // 引入自定义转换器
class FileFormatConvertControllerTest {
    // 原有测试代码不变
}

配置完成后,Spring就能自动将JSON字符串转换为Map,绑定到metadata字段。

方案二:无需JSON序列化,直接通过前缀参数绑定Map

利用Spring MVC的Map参数绑定规则:传递多个以metadata.为前缀的请求参数,Spring会自动将这些参数封装到Map中。

修改测试代码:

@ActiveProfiles("test")
@WebMvcTest(controllers = FileFormatConvertController.class)
class FileFormatConvertControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    void shouldUploadFileWithMetadata() throws Exception {
        MockMultipartFile file = new MockMultipartFile(
                "file",
                "file.docx",
                MediaType.APPLICATION_OCTET_STREAM_VALUE,
                "file content".getBytes()
        );
        mockMvc.perform(multipart("/file")
                                .file(file)
                                .param("title", "File title")
                                .param("metadata.some-key", "some-value") // 用metadata.前缀绑定Map的key-value
                                .param("metadata.another-key", "another-value")
                )
                .andExpect(status().isOk());
    }
}

这种方式无需额外配置转换器,符合表单提交的常规逻辑,参数传递更直观。


内容的提问来源于stack exchange,提问作者rozeus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 17:03:32