Spring Boot CsvMapper bean 在返回使用 Map 创建的 ResponseEntity 时会导致错误

问题描述 投票:0回答:1

在我的 Spring Boot 应用程序中,我有一个被注入到 Service 类中的 CsvMapper。此 CsvMapper 被定义为配置文件中的 bean。

在我的控制器中,当我返回通过传入 HashMap 创建的 ResponseEntity 时,堆栈跟踪会显示这些错误。

    w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Unrecognized column 'message': known columns: ]; nested exception is com.fasterxml.jackson.dataformat.csv.CsvMappingException: Unrecognized column 'message': known columns: ] (through reference chain: java.util.HashMap["message"])]

    w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Unrecognized column 'timestamp': known columns: ]; nested exception is com.fasterxml.jackson.dataformat.csv.CsvMappingException: Unrecognized column 'timestamp': known columns: ] (through reference chain: java.util.LinkedHashMap["timestamp"])]

这是这些文件的简要布局。

CsvParserService

@Slf4j
@Component
public final class CsvParserService {

    private CsvMapper csvMapper;
    // TODO: This injection causes a problem when creating a ResponseEntity(Map,...). Investigate why this is happening
    public CsvParserService(@Qualifier("myCsvMapper") CsvMapper myCsvMapper) {
        this.csvMapper =  myCsvMapper;
    }

    public <T> List<T> parseCsvFile(Class<T> modelClass, MultipartFile csvFile, boolean ifHeader) throws IOException {
        // create inputStream of the scorecard CSV file
        InputStream fileStream = new BufferedInputStream(csvFile.getInputStream());
        CsvSchema csvSchema = csvMapper
                .typedSchemaFor(modelClass)
                .withUseHeader(ifHeader)  // this should be configured applied based on the queryParameter value in the PostRequest
                .withColumnSeparator(',')
                .sortedBy("timestamp", "productName", "a", "b", "c", "d", "e", "f", "g", "h", "j", "k");

        MappingIterator<T> iterator = csvMapper
                .readerWithTypedSchemaFor(modelClass)
                .with(csvSchema)
                .readValues(fileStream);
        return iterator.readAll();
    }

定义bean的配置文件

@Configuration
public class JacksonConfig {
    @Bean(name="myCsvMapper")
    public CsvMapper myCsvMapper(DateTimeFormatter dateTimeFormatter) {
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        configureDateTimeDeSerialization(javaTimeModule, dateTimeFormatter);
        CsvMapper csvMapper = new CsvMapper();
        csvMapper.registerModule(javaTimeModule);
        return csvMapper;
    }

    private void configureDateTimeDeSerialization(JavaTimeModule javaTimeModule, DateTimeFormatter dateTimeFormatter) {

        // Instant customizations
        javaTimeModule.addSerializer(Instant.class, new JsonSerializer<>() {
            @Override
            public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                gen.writeString(dateTimeFormatter.format(value));
            }
        });
        javaTimeModule.addDeserializer(Instant.class, new JsonDeserializer<>() {
            @Override
            public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
                return dateTimeFormatter.parse(p.getText(), Instant::from);
            }
        });

        // Instant customizations for map keys
        javaTimeModule.addKeySerializer(Instant.class, new JsonSerializer<>() {
            @Override
            public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                gen.writeFieldName(dateTimeFormatter.format(value));
            }
        });
        javaTimeModule.addKeyDeserializer(Instant.class, new KeyDeserializer() {
            @Override
            public Object deserializeKey(String key, DeserializationContext ctxt) {
                return dateTimeFormatter.parse(key, Instant::from);
            }
        });
    }

发生错误的控制器在“return new...”的行上

@RepositoryRestController
@RequestMapping("/api/v1")
public class ScorecardSummaryController {

@ResponseBody
@GetMapping(value = {"/csvmapper"})
    public ResponseEntity<?> getProducts() {

    // some services calls
     Map<String, String> map = new HashMap<String, String>();
     map.put("message", "Some message");
     map.put("status", "Ok");
     return new ResponseEntity<>(map, HttpStatus.OK); // error occurs after this line
    }
}

我怀疑 CsvMapper bean 已经成为应用程序的默认映射器?但我不确定这是如何发生的,或者为什么在通过传入 HashMap 创建 ResponseEntity 时会发生这种情况。如果我以其他方式创建 ResponseEntity,例如

return new ResponseEntity<String>("Success", HttpStatus.OK); 
,它不会返回任何错误。如果我在 @Bean 定义中编写代码来忽略未知列的错误,则当我点击 API 端点时应用程序不会返回任何内容。

java spring spring-boot
1个回答
0
投票

我也遇到过同样的问题。 CsvMapper 难以置信地扩展了 ObjectMapper:https://fasterxml.github.io/jackson-dataformats-text/javadoc/csv/2.10/com/fasterxml/jackson/dataformat/csv/CsvMapper.html。 当您创建 CsvMapper bean 时,Spring MVC 在进行 JSON 反/序列化时将使用它而不是它自己的 ObjectMapper。 您有两种选择来解决此问题:

  1. 不要创建 CsvMapper bean,而是在 CsvParserService 构造函数中实例化它。不过,您将失去用于所有应用程序的 CsvMapper 单例的好处。
  2. 除了创建CsvMapper之外,还要创建一个带有@Primary注解的ObjectMapper。请记住添加所有需要的配置。
© www.soinside.com 2019 - 2024. All rights reserved.