8

I am using opencsv to write a Java bean to a CSV file with headers. The file name contains the current date. If the user runs it for a second time in the same day, it appends to the file but adds another header line.

How do I append to the file but without the column headers.

    public class CSVExport {

final File USERHOME = new File(System.getProperty("user.home"));
final List<saleExport> listSaleExport = new ArrayList<>();
final ObjectMapper mapper = new ObjectMapper();

public void createCsvFile(String Region, Map<String, String> currentSale) throws IOException {

    mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
    // use column position mapping strategy for no headers?
    LocalDate today = LocalDate.now();

                final SaleExport saleExport = mapper.convertValue(currentSale, SaleExport.class);
                listSaleExport.add(saleExport);
                writeToFile(today +" LA.csv", listSaleExport);
}

public void writeToFile(String filename, List<listSaleExport> listSaleExport) throws IOException {

    File file = new File(PROCESSED_DIR +"\\", "filename");

    if (!file.exists()) {
        try {
            Writer writer = new FileWriter(PROCESSED_DIR +"\\" +filename, true);
            StatefulBeanToCsvBuilder<listSaleExport> beanToCsv = new StatefulBeanToCsvBuilder<>(writer);
            StatefulBeanToCsv<listSaleExport> beanWriter = beanToCsv.build();
            beanWriter.write(listSaleExport);
            writer.close();
        } catch (Exception ex) {
            System.out.println("Error : " + ex.getMessage());
        }
    } else {
        try {
            Writer writer = new FileWriter(PROCESSED_DIR +"\\" +"filename");
            StatefulBeanToCsvBuilder<listSaleExport> beanToCsv = new StatefulBeanToCsvBuilder<>(writer);
            StatefulBeanToCsv<listSaleExport> beanWriter = beanToCsv.build();
            beanWriter.write(listSaleExport);
            writer.close();
        } catch (Exception ex) {
            System.out.println("Error : " + ex.getMessage());
        }
    }
  }
}
Radika Moonesinghe
  • 361
  • 2
  • 6
  • 17
  • I have a similar question, awaiting an answer: https://stackoverflow.com/questions/54975282/with-opencsv-how-do-i-append-to-existing-csv-using-a-mappingstrategy – djangofan Mar 04 '19 at 00:57

3 Answers3

5

Good one. Appending was something we did not think much of when we did writing in opencsv because it is potentially risky (something goes wrong you could corrupt what was a good file) so write was favored instead.

That said open up a bug or feature request in sourceforge and if there is enough interest we will try and get it in the 4.3 release (4.2 is booked solid).

THAT said if you want to get around this create your own MappingStrategy class that extends the HeaderColumnNameMappingStrategy and all you need there is a override on the generateHeader method to return an empty String array. You can look at the code in ColumnPositionMappingStrategy to see what I am talking about. This will prevent the header from being written. In this case you will need to use this only in the else part.

Hope that helps.

:)

Scott Conway
  • 975
  • 7
  • 13
  • 2
    Overriding of generateHeader method does not work. The writer will write an empty line. – Finkelson Aug 01 '18 at 16:11
  • Hi, sorry but overriding generateHeader adds a whole empty line. What is the correct way to do this? – Usr Mar 25 '19 at 15:58
  • 1
    If you are using ColumnPositionMappingStrategy you should specify position with @CsvBindByPosition annotion under each field in the model class – Deplake Jan 23 '20 at 14:32
  • What about considering adding @afarrapeira 's code (see https://stackoverflow.com/a/60222383/363573) in opencsv project. It would be a valuable contribution. – Stephan Mar 20 '21 at 14:39
5

I don't know if the feature was finally added in OpenCSV. Right now the latest version is 5.1 and I haven't read anything about it...

As I've posted in another question, I got it to work after updating to 5.1:

Needed to have CSVs with both specific field headers (@CsvBindByName) and positions (@CsvBindByPosition), so I already had to make my own MappingStrategy.

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

}

I also made a generic exporter class, to ease CSV generation:

public class ExportGenericCSV<T> {
    private static final Logger logger = LoggerFactory.getLogger(ExportGenericCSV.class);
    private final char DEFAULT_QUOTE_CHARACTER = CSVWriter.NO_QUOTE_CHARACTER;
    private final char CSV_SEPARATOR = CSVWriter.DEFAULT_SEPARATOR;
    
    public void writeToFile(String fileName, List<T> content){
        writeList(fileName, content, true);
    }
    
    public void appendToFile(String fileName, List<T> content){
        writeList(fileName, content, false, StandardOpenOption.APPEND);
    }

    @SuppressWarnings("unchecked")
    private void writeList(String fileName, List<T> exports, boolean addHeader, OpenOption...openOptions){
        if(exports == null  || exports.isEmpty()) return;
        try (Writer writer = Files.newBufferedWriter(Paths.get(fileName), openOptions)){

            CustomMappingStrategy<T> mapping = new CustomMappingStrategy<T>(addHeader);
            mapping.setType((Class<? extends T>) exports.get(0).getClass());
            
            StatefulBeanToCsv<T> beanToCsv = new StatefulBeanToCsvBuilder<T>(writer)
                    .withQuotechar(DEFAULT_QUOTE_CHARACTER)
                    .withSeparator(CSV_SEPARATOR)
                    .withMappingStrategy(mapping)
                    .build();

            beanToCsv.write(exports);
            
            writer.flush();
            writer.close();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        } catch (CsvDataTypeMismatchException e) {
            logger.error(e.getMessage(), e);
        } catch (CsvRequiredFieldEmptyException e) {
            logger.error(e.getMessage(), e);
        }
    }
}

This way, when you need to export a list of items you can use:

ExportGenericCSV<ExportedClass> exporter = new ExportGenericCSV<ExportedClass>();

and then use exporter.writeToFile(...) or exporter.appendToFile(...)

afarrapeira
  • 740
  • 8
  • 14
  • 1
    You may propose your valuable implementation as a contribution to the opencsv project. Appending to an existing CSV file is a **heavily** missing feature in opencsv project ! – Stephan Mar 20 '21 at 14:36
0

You can use this simple mappingStrategy. Please, take a look at return new String[0];

// 1. how to use:
StatefulBeanToCsv beanToCsv = new StatefulBeanToCsvBuilder(writer)
    .withMappingStrategy(newCsvAnnotationStrategy(list.iterator().next().getClass(), writeHeader))
    .build();
beanToCsv.write(list);

// 2. mappingStrategy
/**
 *
 * @author Alexander Ryasnyanskiy
 * created on 2022-06-10
 */
public class CsvAnnotationStrategy<T> extends HeaderColumnNameTranslateMappingStrategy<T> {
    private final boolean writeHeader;

    public CsvAnnotationStrategy(Class<T> type, boolean writeHeader) {
        this.writeHeader = writeHeader;
        Map<String, String> map = new HashMap<>();
        for (Field field : type.getDeclaredFields()) {
            map.put(field.getName(), field.getName());
        }
        setType(type);
        setColumnMapping(map);
    }

    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        String[] result = super.generateHeader(bean);
        for (int i = 0; i < result.length; i++) {
            result[i] = getColumnName(i);
        }
        if (writeHeader) {
            return result;
        } else {
            return new String[0];
        }
    }

}