OpenCsv writes wrong column names with BeanToCsv + HeaderColumnNameTranslateMappingStrategy

16,528

Solution 1

Looking at the source code of BeanToCsv, the processHeader(...) method does nothing with the supplied headers. Your only option is to create a custom strategy ( to avoid CSVReader ) and a custom BeanToCsv as below

 

public class CustomBean {
   ...
   public static void main(String[] args) {
     ...
     HeaderColumnNameTranslateMappingStrategy strategy = new CustomStrategy<CustomBean>();
     strategy.setType(CustomBean.class);
     strategy.setColumnMapping(mapping);
     ...

     BeanToCsv bean = new CustomBeanToCsv<CustomBean>();
     ...
   }

   static class CustomStrategy<T> extends HeaderColumnNameTranslateMappingStrategy {

    @Override
    public void setColumnMapping(Map columnMapping) {
        super.setColumnMapping(columnMapping);
        header = new String[columnMapping.size()];
        int i = 0;
        for (Map.Entry entry : columnMapping.entrySet()) {
            header[i] = entry.getKey().toUpperCase();
            i++;
        }
    }

     public String[] getHeader() {
         return header;
     }
   }

   static class CustomBeanToCsv<T> extends BeanToCsv {
     @Override
     protected String[] processHeader(MappingStrategy mapper) throws IntrospectionException {
         if (mapper instanceof CustomStrategy) {
             return ((CustomStrategy) mapper).getHeader();
         } else {
             return super.processHeader(mapper);
         }
     }
   }
}

 

Solution 2

I have been using openCSV for five years now and I am still learning stuff about it. The issue is that the HeaderColumnNameMappingStrategy and HeaderColumnNameTranslateMappingStrategy were made for the CsvToBean. It wants a file to read to get the header out to see where the reader should read from.

For the BeanToCsv class use the ColumnPositionMappingStrategy class. You give it the class you are mapping and a list of the columns you want to map and it does the rest for you.

Here is a little test method I wrote that worked.

public void createUsingBeanToCsv(int numRecords, FileWriter fos) {
    List<SmallRecord> smallRecords = new ArrayList<>(numRecords);
    for (int i = 0; i < numRecords; i++) {
        smallRecords.add(SmallRecordGenerator.createRecord(i));
    }

    BeanToCsv<SmallRecord> beanToCsv = new BeanToCsv<>();
    ColumnPositionMappingStrategy<SmallRecord> strategy = new ColumnPositionMappingStrategy<>();
    strategy.setType(SmallRecord.class);
    String[] columns = new String[]{"bigDecimal_1", "name_1", "intNumber_1"};
    strategy.setColumnMapping(columns);

    beanToCsv.write(strategy, fos, smallRecords);

}
Share:
16,528
Baduel
Author by

Baduel

IT consultant at Technology Reply (Oracle Platinum Partner).

Updated on June 14, 2022

Comments

  • Baduel
    Baduel almost 2 years

    I'm using opencsv 3.6 in order to create a csv file starting from a java bean.

    First of all, I tried this code:

    import com.opencsv.CSVReader;
    import com.opencsv.CSVWriter;
    import com.opencsv.bean.BeanToCsv;
    import com.opencsv.bean.HeaderColumnNameTranslateMappingStrategy;
    
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.IOException;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Map;
    
    public class CustomBean {
    
        private String name;
        private String surname;
    
        public CustomBean(String n, String s) {
            this.name = n;
            this.surname = s;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setSurname(String surname) {
            this.surname = surname;
        }
    
        public String getSurname() {
            return surname;
        }
    
        public static void main(String[] args) {
            Map<String,String> mapping = new HashMap<String,String>();
            mapping.put("COLUMN1","name");
            mapping.put("COLUMN2","surname");
    
            HeaderColumnNameTranslateMappingStrategy<CustomBean> strategy = new HeaderColumnNameTranslateMappingStrategy<CustomBean>();
            strategy.setType(CustomBean.class);
            strategy.setColumnMapping(mapping);
    
            ArrayList<CustomBean> customUsers = new ArrayList<CustomBean>();
            customUsers.add(new CustomBean("Kobe","Bryant"));
    
            BeanToCsv<CustomBean> bean = new BeanToCsv<CustomBean>();
    
            try {            
                CSVWriter writer = new CSVWriter(new FileWriter("testOut.csv"));
                bean.write(strategy, writer, customUsers);
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    }
    

    But I had the following error:

    Exception in thread "main" java.lang.RuntimeException: Error writing CSV !
        at com.opencsv.bean.BeanToCsv.write(BeanToCsv.java:74)
        at test.CustomBean.main(CustomBean.java:63)
    Caused by: java.lang.NullPointerException
        at com.opencsv.bean.HeaderColumnNameTranslateMappingStrategy.getColumnName(HeaderColumnNameTranslateMappingStrategy.java:45)
        at com.opencsv.bean.HeaderColumnNameMappingStrategy.findDescriptor(HeaderColumnNameMappingStrategy.java:112)
        at com.opencsv.bean.BeanToCsv.processHeader(BeanToCsv.java:103)
        at com.opencsv.bean.BeanToCsv.write(BeanToCsv.java:69)
        ... 1 more
    

    This happens because in opencsv source code in getColumnName method in the HeaderColumnNameTranslateMappingStrategy class there is the following line:

    return col < header.length ? columnMapping.get(header[col].toUpperCase()) : null;
    

    Therefore, header is null. This is true, in fact this class is a subclass of HeaderColumnNameMappingStrategy class that contains the header variable (String[] type) that is never initialized.

    The only useful method I found in this class is captureHeader, but unfortunately it takes a CSVReader as input.

    For this reason I created an empty csv file:

    COLUMN1,COLUMN2
    

    and I added the following lines at the beginning of the try/catch block:

    CSVReader reader = new CSVReader(new FileReader("testIn.csv"));
    strategy.captureHeader(reader);
    

    In this way (that I really don't like because I have to create a csv file) I have no exception, but in the result csv file the name of the columns does not follow the mapping strategy:

    "name","surname"
    "Kobe","Bryant"
    

    Two questions:

    1. How can I have the expected result, i.e. the right column names in the csv file?
    2. There is a way to not use the CSVReader class?