Search code examples
javacsvopencsv

OpenCSV - create row for every entry in list of maps


I am trying to implement an DMN (Decision Model and Notation) evaluation service, where the user can upload a csv file with test cases to be evaluated and receive results also as a csv file for every test cases in the input file.

Reading the input csv file and evaluating the test cases works without problems. But I have some issues in writing the results to a csv file using OpenCsv.

Here is the mapped bean, which should be converted to csv row:

@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;
}

As you can see here, the test case result can have in some situations multiple testOutputs, defined as a list of map.

What I want is to write for every map entry in the testOutput, a seperate row in the csv file. But with the code I wrote below, only the first entry of the testOutput is written as only one row in the csv file.

 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());
    }
}

How can I tell the OpenCsv that it should create seperate row for each entry in the testOutputs ?

EDIT: Added more information

UI: enter image description here

Resulted incorrect CSV: enter image description here

Expected correct CSV: enter image description here

As you can see from the screenshots, one input can have multiple test outputs. Therefore I want to create for every test output a seperate line in csv file.


Solution

  • As StatefulBeanToCsv does not seem to be capable to generating multiple lines for a single bean, I suggest implementing a custom mapping function. This also requires you to manually print the header line as well.

    public static 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)) {
            writeHeader(writer);
            for (DmnTestCaseResult r : results) {
                for (Map<String, Object> map : r.getTestOutput())
                    writer.writeNext(map(r, map));
            }
            return sw.toString();
        } catch (Exception ex) {
            throw new RuntimeException(ex.getMessage());
        }
    }
    
    private static void writeHeader(CSVWriter writer) {
        List<String> header = new ArrayList<>();
        header.add("ERRORMESSAGE");
        header.add("EXPECTEDOUTPUT");
        header.add("INPUT");
        header.add("OUTPUT");
        writer.writeNext(header.toArray(new String[] {}));
    
    }
    
    private static String[] map(DmnTestCaseResult r, Map<String, Object> testOutput) {
        // you can manually adjust formats here as well; entrySet() call can be left out, it does change the format. do what you like more
        List<String> line = new ArrayList<>();
        line.add(r.getErrorMessage());
        line.add(r.getExpectedOutput().entrySet().toString());
        line.add(r.getTestInput().entrySet().toString());
        line.add(testOutput.entrySet().toString());
        return line.toArray(new String[] {});
    }
    

    And this prints:

    ERRORMESSAGE,EXPECTEDOUTPUT,INPUT,OUTPUT
    errorMessage,[expectedOutput1=expectedOutput1, expectedOutput2=expectedOutput2],[input2=testInput2, input1=testInput1],[testOut2=testOut2, testOut=testOut1]
    errorMessage,[expectedOutput1=expectedOutput1, expectedOutput2=expectedOutput2],[input2=testInput2, input1=testInput1],[testOut3=testOut3, testOut4=testOut4]