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
相关产品推荐
相关产品推荐

