Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

With OpenCSV, how do I append to existing CSV using a MappingStrategy?

Tags:

opencsv

With OpenCSV, how do I append to existing CSV using a MappingStrategy? There are lots of examples I could find where NOT using a Bean mapping stategy BUT I like the dynamic nature of the column mapping with bean strategy and would like to get it working this way. Here is my code, which just rewrites the single line to CSV file instead of appending.

How can I fix this? Using OpenCSV 4.5 . Note: I set my FileWriter for append=true . This scenario is not working as I expected. Re-running this method simply results in over-writing the entire file with a header and a single row.

public void addRowToCSV(PerfMetric rowData) {
    File file = new File(PerfTestMetric.CSV_FILE_PATH);
    try {
        CSVWriter writer = new CSVWriter(new FileWriter(file, true));

        CustomCSVMappingStrategy<PerfMetric> mappingStrategy 
          = new CustomCSVMappingStrategy<>();
        mappingStrategy.setType(PerfMetric.class);

        StatefulBeanToCsv<PerfMetric> beanToCsv 
           = new StatefulBeanToCsvBuilder<PerfMetric>(writer)
            .withMappingStrategy(mappingStrategy)
            .withSeparator(',')
            .withApplyQuotesToAll(false)
            .build();

        try {
            beanToCsv.write(rowData);
        } catch (CsvDataTypeMismatchException e) {
            e.printStackTrace();
        } catch (CsvRequiredFieldEmptyException e) {
            e.printStackTrace();
        }
        writer.flush();
        writer.close();
    } catch (IOException e) {
            e.printStackTrace();
    }
}

Or, is the usual pattern to load all rows into a List and then re-write entire file? I was able to get it to work by writing two MappingStrategy mapping strategies and then conditionally using them with a if-file-exists but doing it that way leaves me with a "Unchecked assignment" warning in my code. Not ideal; hoping for an elegant solution?

like image 840
djangofan Avatar asked Sep 10 '25 16:09

djangofan


2 Answers

I've updated OpenCSV to version 5.1 and got it working. In my case I needed the CSV headers to have a specific name and position, so I'm using both @CsvBindByName and @CsvBindByPosition, and needed to create a custom MappingStrategy to get it working.

Later, I needed to edit the MappingStrategy to enable appending, so when it's in Appending mode I don't need to generate a CSV header.

public class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
    private boolean useHeader=true;

    public CustomMappingStrategy(){
    }

    public CustomMappingStrategy(boolean useHeader) {
        this.useHeader = useHeader;
    }

    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        final int numColumns = FieldUtils.getAllFields(bean.getClass()).length;
        super.setColumnMapping(new String[numColumns]);

        if (numColumns == -1) {
            return super.generateHeader(bean);
        }

        String[] header = new String[numColumns];

        if(!useHeader){
            return ArrayUtils.EMPTY_STRING_ARRAY;
        }
        BeanField<T, Integer> beanField;
        for (int i = 0; i < numColumns; i++){
            beanField = findField(i);
            String columnHeaderName = extractHeaderName(beanField);
            header[i] = columnHeaderName;
        }

        return header;
    }

    private String extractHeaderName(final BeanField<T, Integer> beanField){
        if (beanField == null || beanField.getField() == null || beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class).length == 0){
            return StringUtils.EMPTY;
        }

        //return value of CsvBindByName annotation
        final CsvBindByName bindByNameAnnotation = beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class)[0];
        return bindByNameAnnotation.column();
    }

}

Now if you use the default constructor it'll add the header to the generated CSV, and using a boolean you can tell it to add a header or to ignore it.

like image 158
afarrapeira Avatar answered Sep 13 '25 05:09

afarrapeira


I never found an answer to this question and so what I ended up doing was doing a branch if-condition where .csv file exists or not. If file exists I used MappingStrategyWithoutHeader strategy, and if file didn't yet exist, I used MappingStrategyWithHeader strategy. Not ideal, but I got it working.

like image 35
djangofan Avatar answered Sep 13 '25 04:09

djangofan