12

I am using opencsv-4.0 to write a csv file and I need to add column headers in output file.

Here is my code.

public static void buildProductCsv(final List<Product> product,
        final String filePath) {

    try {

        Writer writer = new FileWriter(filePath);

        // mapping of columns with their positions
        ColumnPositionMappingStrategy<Product> mappingStrategy = new ColumnPositionMappingStrategy<Product>();
        // Set mappingStrategy type to Product Type
        mappingStrategy.setType(Product.class);
        // Fields in Product Bean
        String[] columns = new String[] { "productCode", "MFD", "EXD" };
        // Setting the colums for mappingStrategy
        mappingStrategy.setColumnMapping(columns);

        StatefulBeanToCsvBuilder<Product> builder = new StatefulBeanToCsvBuilder<Product>(writer);

        StatefulBeanToCsv<Product> beanWriter = builder.withMappingStrategy(mappingStrategy).build();
        // Writing data to csv file
        beanWriter.write(product);
        writer.close();

        log.info("Your csv file has been generated!");

    } catch (Exception ex) {
        log.warning("Exception: " + ex.getMessage());
    }

}

Above code create a csv file with data. But it not include column headers in that file.

How could I add column headers to output csv?

Bishan
  • 14,035
  • 48
  • 151
  • 235

5 Answers5

20

ColumnPositionMappingStrategy#generateHeader returns empty array

/**
 * This method returns an empty array.
 * The column position mapping strategy assumes that there is no header, and
 * thus it also does not write one, accordingly.
 * @return An empty array
 */
@Override
public String[] generateHeader() {
    return new String[0];
}

If you remove MappingStrategy from BeanToCsv builder

// replace 
StatefulBeanToCsv<Product> beanWriter = builder.withMappingStrategy(mappingStrategy).build();
// with
StatefulBeanToCsv<Product> beanWriter = builder.build(); 

It will write Product's class members as CSV header

If your Product class members names are

"productCode", "MFD", "EXD"

This should be the right solution

Else, add @CsvBindByName annotation

import com.opencsv.bean.CsvBindByName;
import com.opencsv.bean.StatefulBeanToCsv;
import com.opencsv.bean.StatefulBeanToCsvBuilder;

import java.io.FileWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

public class CsvTest {

    public static void main(String[] args) throws Exception {
        Writer writer = new FileWriter(fileName);

        StatefulBeanToCsvBuilder<Product> builder = new StatefulBeanToCsvBuilder<>(writer);
        StatefulBeanToCsv<Product> beanWriter = builder.build();

        List<Product> products = new ArrayList<>();
        products.add(new Product("1", "11", "111"));
        products.add(new Product("2", "22", "222"));
        products.add(new Product("3", "33", "333"));
        beanWriter.write(products);
        writer.close();
    }

    public static class Product {
        @CsvBindByName(column = "productCode")
        String id;
        @CsvBindByName(column = "MFD")
        String member2;
        @CsvBindByName(column = "EXD")
        String member3;

        Product(String id, String member2, String member3) {
            this.id = id;
            this.member2 = member2;
            this.member3 = member3;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getMember2() {
            return member2;
        }

        public void setMember2(String member2) {
            this.member2 = member2;
        }

        public String getMember3() {
            return member3;
        }

        public void setMember3(String member3) {
            this.member3 = member3;
        }
    }

}

Output:

"EXD","MFD","PRODUCTCODE"

"111","11","1"

"222","22","2"

"333","33","3"

Pay attention; class, getters & setters needs to be public due to the use of Reflection by OpenCSV library

Community
  • 1
  • 1
whoopdedoo
  • 2,466
  • 16
  • 41
  • Thanks for your answer. This worked. But Is there any way to change the column order? As you can see columns are ordered in alphabetical. I need to customize the order like `"PRODUCTCODE", "EXD","MFD"` – Bishan Sep 07 '17 at 04:14
  • 1
    @Bishan you can add to each member the annotation @CsvBindByPosition(position = 0), position is zero based. – whoopdedoo Sep 07 '17 at 08:38
  • 2
    Can't I use both `@CsvBindByPosition` and `@CsvBindByName`? I have added `@CsvBindByPosition`. Now column order is ok. But column names are missing in output file. – Bishan Sep 07 '17 at 10:26
  • 1
    it seems like you are right, i've been traveling all around the source code, consider open a ticket at https://sourceforge.net/p/opencsv/feature-requests/ – whoopdedoo Sep 07 '17 at 11:27
  • Thanks for your cooperation – Bishan Sep 07 '17 at 11:28
  • 1
    no problem, if annotations CsvBindByPosition or CsvCustomBindByPosition are present, ColumnPositionMappingStrategy is chosen, which does not generate headers, ColumnPositionMappingStrategy#generateHeader returns empty array – whoopdedoo Sep 07 '17 at 11:29
  • @Bishan it will be nice if you accept my answer as a solution, there are many and might be more suitable CSV libraries for your product, take a look https://github.com/search?l=Java&o=desc&q=CSV+library&s=stars&type=Repositories – whoopdedoo Sep 07 '17 at 12:32
  • @Bishan Please have a look at [my answer to similar question](https://stackoverflow.com/questions/45203867/how-to-write-from-bean-to-csv-using-opencsv-by-specfying-custom-column-headers/46185186#46185186). It shows how to write CSV and have both custom header column names and ordering – sebast26 Sep 12 '17 at 21:17
  • 1
    Is there anyway to get the exact name in the CsvBindByName annotation? and not the UPPERCASE of it? – Udi Feb 11 '19 at 07:56
  • @Iddo how can i remove the double quotes from the output value ? – Pranav MS Apr 30 '19 at 09:02
  • Some solution how to control custom naming and ordering: https://stackoverflow.com/a/58731084/7230670 – Vladyslav Vynnyk Nov 06 '19 at 13:24
5

I may have missed something obvious here but couldn't you just append your header String to the writer object?

Writer writer = new FileWriter(filePath);
writer.append("header1, header2, header3, ...etc \n");

// This will be followed by your code with BeanToCsvBuilder 
// Note: the terminating \n might differ pending env.
Ithar
  • 3,186
  • 3
  • 30
  • 35
1

You can append by annotation

public void export(List<YourObject> list, PrintWriter writer) throws Exception {
        writer.append( buildHeader( YourObject.class ) );
        StatefulBeanToCsvBuilder<YourObject> builder = new StatefulBeanToCsvBuilder<>( writer );
        StatefulBeanToCsv<YourObject> beanWriter = builder.build();
        beanWriter.write( mapper.map( list ) );
        writer.close();
    }

    private String buildHeader(Class<YourObject> clazz) {
        return Arrays.stream( clazz.getDeclaredFields() )
                .filter( f -> f.getAnnotation( CsvBindByPosition.class ) != null
                        && f.getAnnotation( CsvBindByName.class ) != null )
                .sorted( Comparator.comparing( f -> f.getAnnotation( CsvBindByPosition.class ).position() ) )
                .map( f -> f.getAnnotation( CsvBindByName.class ).column() )
                .collect( Collectors.joining( "," ) ) + "\n";
    }

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class YourObject {

    @CsvBindByPosition(position = 0)
    @CsvBindByName(column = "A")
    private Long a;

    @CsvBindByPosition(position = 1)
    @CsvBindByName(column = "B")
    private String b;

    @CsvBindByPosition(position = 2)
    @CsvBindByName(column = "C")
    private String c;

}
0

Use a HeaderColumnNameMappingStrategy for reading, then use the same strategy for writing. "Same" in this case meaning not just the same class, but really the same object.

From the 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.

This way you will get a CSV including headers, with columns in the same order as the original CSV.

Worked for me using OpenCSV 5.4.

Yonas
  • 108
  • 4
-1

You could also override the generateHeaders method and return the column mapping that is set, which will have header row in csv

ColumnPositionMappingStrategy<Product> mappingStrategy = new ColumnPositionMappingStrategy<Product>() {
            @Override
            public String[] generateHeader(Product bean) throws CsvRequiredFieldEmptyException {
                return this.getColumnMapping();
            }
        };