Writing csv file with OpenCsv without capitalized headers and follows declaration order

Franz Wong - Jun 5 '22 - - Dev Community

(OpenCSV 5.6 is used)

Problem

This class describes each CSV row. It has only 2 columns. id is the 1st column and country_code is the 2nd column.

public class CsvRow {

    @CsvBindByName(column = "id")
    private String id;

    @CsvBindByName(column = "country_code")
    private String countryCode;

    public CsvRow(String id, String countryCode) {
        this.id = id;
        this.countryCode = countryCode;
    }

}
Enter fullscreen mode Exit fullscreen mode

CSV file is generated by StatefulBeanToCsv.

public class Application {
    public static void main(String[] args) throws Exception {
        CsvRow[] csvRows = new CsvRow[] {
                new CsvRow("HK", "852"),
                new CsvRow("US", "1"),
                new CsvRow("JP", "81"),
        };

        Path outputPath = Path.of("countries.csv");
        try (var writer = Files.newBufferedWriter(outputPath)) {
            StatefulBeanToCsv<CsvRow> csv = new StatefulBeanToCsvBuilder<CsvRow>(writer)
                    .build();
            csv.write(Arrays.asList(csvRows));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here is the csv file generated. All headers are capitalized and the order of headers are sorted by alphabetical order.

"COUNTRY_CODE","ID"
"852","HK"
"1","US"
"81","JP"
Enter fullscreen mode Exit fullscreen mode

But this is what we expect.

"id","country_code"
"HK","852"
"US","1"
"JP","81"
Enter fullscreen mode Exit fullscreen mode

Solution

We have to use mapping strategy.

From javadoc of StatefulBeanToCsvBuilder.withMappingStrategy,

It is perfectly legitimate to read a CSV source, take the mapping strategy from the read operation, and pass it in to this method for a write operation. This conserves some processing time, but, more importantly, preserves header ordering.

It means that we need to initialize a mapping strategy by reading an existing CSV file. We can programmatically create a CSV file and read it.

// Create our strategy
HeaderColumnNameMappingStrategy<CsvRow> strategy = new HeaderColumnNameMappingStrategy<>();
strategy.setType(CsvRow.class);

// Build the header line which respects the declaration order
String headerLine = Arrays.stream(CsvRow.class.getDeclaredFields())
        .map(field -> field.getAnnotation(CsvBindByName.class))
        .filter(Objects::nonNull)
        .map(CsvBindByName::column)
        .collect(Collectors.joining(","));

// Initialize strategy by reading a CSV with header only
try (StringReader reader = new StringReader(headerLine)) {
    CsvToBean<CsvRow> csv = new CsvToBeanBuilder<CsvRow>(reader)
            .withType(CsvRow.class)
            .withMappingStrategy(strategy)
            .build();
    for (CsvRow csvRow : csv) {}
}
Enter fullscreen mode Exit fullscreen mode

Now we can pass the strategy to StatefulBeanToCsvBuilder.

StatefulBeanToCsv<CsvRow> csv = new StatefulBeanToCsvBuilder<CsvRow>(writer)
    .withMappingStrategy(strategy)
    .build();
Enter fullscreen mode Exit fullscreen mode

Re-execute the application and we will have the CSV file we expected.

"id","country_code"
"HK","852"
"US","1"
"JP","81"
Enter fullscreen mode Exit fullscreen mode

This is how the application looks like.

public class Application {
    public static void main(String[] args) throws Exception {
        CsvRow[] csvRows = new CsvRow[] {
                new CsvRow("HK", "852"),
                new CsvRow("US", "1"),
                new CsvRow("JP", "81"),
        };

        HeaderColumnNameMappingStrategy<CsvRow> strategy = new HeaderColumnNameMappingStrategy<>();
        strategy.setType(CsvRow.class);

        String headerLine = Arrays.stream(CsvRow.class.getDeclaredFields())
                .map(field -> field.getAnnotation(CsvBindByName.class))
                .filter(Objects::nonNull)
                .map(CsvBindByName::column)
                .collect(Collectors.joining(","));

        try (StringReader reader = new StringReader(headerLine)) {
            CsvToBean<CsvRow> csv = new CsvToBeanBuilder<CsvRow>(reader)
                    .withType(CsvRow.class)
                    .withMappingStrategy(strategy)
                    .build();
            for (CsvRow csvRow : csv) {}
        }

        Path outputPath = Path.of("countries.csv");
        try (var writer = Files.newBufferedWriter(outputPath)) {
            StatefulBeanToCsv<CsvRow> csv = new StatefulBeanToCsvBuilder<CsvRow>(writer)
                    .withMappingStrategy(strategy)
                    .build();
            csv.write(Arrays.asList(csvRows));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player