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

OpenCSV如何为List<Map>类型字段的每个元素单独生成CSV行

问题描述

我正在实现一个DMN(Decision Model and Notation,决策模型与表示法)评估服务,支持用户上传包含测试用例的CSV文件,服务完成评估后将输入文件中所有测试用例的结果以CSV格式返回。
目前读取输入CSV文件、执行测试用例评估的流程可正常运行,但在使用OpenCsv将结果写入CSV文件时遇到问题。

待转换为CSV行的映射Bean定义如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DmnTestCaseResult {

   private Map<String, Object> testInput;

   private Map<String, Object> expectedOutput;

   private List<Map<String, Object>> testOutput;

   private String errorMessage;
}

部分场景下测试用例结果会包含多个testOutput,该字段为Map组成的列表。我的需求是为testOutput列表中的每一个Map条目单独生成CSV文件中的一行,但使用如下编写的转换代码时,仅会写入testOutput的第一个条目,单个测试用例仅生成一行CSV记录:

public String convertDmnRuleTestResultToCsv(DmnRuleTestResult result) {
    List<DmnTestCaseResult> results = result.getTestCases();
    try(StringWriter sw = new StringWriter(); CSVWriter writer = new CSVWriter(sw, CSVWriter.DEFAULT_SEPARATOR, CSVWriter.NO_QUOTE_CHARACTER, CSVWriter.NO_ESCAPE_CHARACTER, CSVWriter.DEFAULT_LINE_END)) {
        StatefulBeanToCsv<DmnTestCaseResult> beanToCsv = new StatefulBeanToCsvBuilder<DmnTestCaseResult>(writer)
                .withApplyQuotesToAll(false)
                .build();
        beanToCsv.write(results);
        return sw.toString();
    } catch(Exception ex){
        throw new CsvParseException(ex.getMessage());
    }
}

补充说明:单条测试输入可能对应多条测试输出,因此需要为每一条测试输出单独生成CSV中的一行。

解决方案

OpenCsv的StatefulBeanToCsv默认遵循「一个Java Bean对应一行CSV记录」的逻辑,没有提供任何配置项可以自动将Bean内的列表字段拆分为多行,不需要在序列化配置上浪费时间,最直接、可维护性最高的方案是在写入前先将原始结果集做扁平化处理,步骤如下:

  • 第一步:定义扁平化后的单行记录Bean,去掉列表类型的testOutput字段,替换为单个testOutput条目对应的Map,每个字段直接对应CSV的一列:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FlatDmnTestCaseRow {
    private Map<String, Object> testInput;
    private Map<String, Object> expectedOutput;
    private Map<String, Object> singleTestOutput;
    private String errorMessage;
}
  • 第二步:遍历原始测试用例结果集,将每个测试用例按testOutput条目拆分为多行。如果测试用例没有输出(比如执行报错场景),也保留一行记录,将singleTestOutput置空即可:
List<FlatDmnTestCaseRow> flatRows = new ArrayList<>();
for (DmnTestCaseResult testCase : results) {
    List<Map<String, Object>> testOutputs = testCase.getTestOutput();
    // 处理无输出、执行报错的场景,保证测试用例不丢失
    if (testOutputs == null || testOutputs.isEmpty()) {
        flatRows.add(new FlatDmnTestCaseRow(
                testCase.getTestInput(),
                testCase.getExpectedOutput(),
                null,
                testCase.getErrorMessage()
        ));
        continue;
    }
    // 每个测试输出单独生成一行记录,复用同一条用例的输入、期望输出、错误信息
    for (Map<String, Object> output : testOutputs) {
        flatRows.add(new FlatDmnTestCaseRow(
                testCase.getTestInput(),
                testCase.getExpectedOutput(),
                output,
                testCase.getErrorMessage()
        ));
    }
}
  • 第三步:将原来写入原始DmnTestCaseResult列表的逻辑,替换为写入扁平化后的flatRows列表即可,其余OpenCsv配置不需要改动,输出的CSV就会自动满足「单条测试输出对应一行」的要求。

这种方案比自定义OpenCsv序列化策略、修改Bean注解的实现成本低很多,后续调整列顺序、新增/删除字段、修改输出规则都不需要改动底层序列化逻辑,维护成本极低。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 23:06:28