StatefulBeanToCsv with Column headers
Asked Answered
C

8

23

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?

Cassino answered 6/9, 2017 at 10:31 Comment(2)
Better related answers here - #45204367Propylene
Does this answer your question? OpenCSV: How to create CSV file from POJO with custom column headers and custom column positions?Propylene
S
23

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

Soloman answered 6/9, 2017 at 11:2 Comment(12)
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"Cassino
@Cassino you can add to each member the annotation @CsvBindByPosition(position = 0), position is zero based.Soloman
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.Cassino
it seems like you are right, i've been traveling all around the source code, consider open a ticket at sourceforge.net/p/opencsv/feature-requestsSoloman
Thanks for your cooperationCassino
no problem, if annotations CsvBindByPosition or CsvCustomBindByPosition are present, ColumnPositionMappingStrategy is chosen, which does not generate headers, ColumnPositionMappingStrategy#generateHeader returns empty arraySoloman
@Cassino 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 github.com/…Soloman
@Cassino Please have a look at my answer to similar question. It shows how to write CSV and have both custom header column names and orderingArdra
Is there anyway to get the exact name in the CsvBindByName annotation? and not the UPPERCASE of it?Isotherm
@Iddo how can i remove the double quotes from the output value ?Vanhouten
Some solution how to control custom naming and ordering: https://mcmap.net/q/264980/-opencsv-how-to-create-csv-file-from-pojo-with-custom-column-headers-and-custom-column-positionsLowther
this didn't work for me, without the mapper strategy even with @CsvBindByName the header columns are still missingNonbeliever
R
11

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;

}
Rojas answered 6/4, 2020 at 13:50 Comment(2)
Elegant use of stream. This seems the best implementation.Jamshid
This is the best solution - better than using a CustomMappingStrategyMartin
P
6

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.
Polyneuritis answered 11/6, 2018 at 12:15 Comment(1)
If you refactor your POJO you'll have to remember to change that line too.Unstriped
C
2

For those who want to use HeaderName and Position together without reading a csv file first.

It's quite easy to achieve using a custom Mapping Strategy.

static class NameAndPositionMappingStrategy<T> extends HeaderNameBaseMappingStrategy<T> {
        public NameAndPositionMappingStrategy(Class<T> clazz) {
            setType(clazz);
            setColumnOrderOnWrite(new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    CsvBindByPosition pos1 = getFieldMap().get(o1).getField().getAnnotation(CsvBindByPosition.class);
                    CsvBindByPosition pos2 = getFieldMap().get(o2).getField().getAnnotation(CsvBindByPosition.class);
                    return Integer.compare(
                       pos1 != null ? pos1.position() : 0,
                       pos2 != null ? pos2.position() : 0
                    );
                }
            });
        }
    }

And in your bean you can now define both annotations

 public static class Product {
        @CsvBindByPosition(position = 0)
        @CsvBindByName(column = "ID")
        String id;

        @CsvBindByPosition(position = 1)
        @CsvBindByName(column = "FIELD2")
        String member2;

        @CsvBindByPosition(position = 2)
        @CsvBindByName(column = "FIELD3")
        String member3;
    

}

And the Writer should look like this:


builder.withMappingStrategy(new NameAndPositionMappingStrategy<Product>(Product.class).build()

This will use the CsvBindByName and CsvBindByPosition together.

(Alternatively you can also just use the HeaderNameBaseMappingStrategy and set the ColumnOrderOnWrite without using inheritance)

Coterie answered 19/9, 2023 at 14:13 Comment(1)
Thanks, you saved my day! This should be included to opencsv by default.Monosepalous
S
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.

Squadron answered 31/3, 2021 at 8:21 Comment(0)
T
0

Use a custom strategy

    static class CustomStrategy<T> extends ColumnPositionMappingStrategy<T> {
    public String[] generateHeader() {
        return this.getColumnMapping();
    }
}

and on CSV object that you write do not forget to provide both

@CsvBindByName(column="UID")
@CsvBindByPosition(position = 3)
Thermocline answered 25/7, 2022 at 21:30 Comment(1)
Doesn't work for meCoterie
B
0

I have struggled for the same issue for a couple of hours.. Finally I found this solution for mapping the right column for POJO and output with header with the opencsv. Hope this can help you a little bit.

There are 2 points we need to pay extra attention.

  1. Must add both of the annotations(e.g. @CsvBindByPosition(position = 0) and @CsvBindByName(column = "var_0")) on the field in the POJO.
  2. Must configure the right mapping strategy and apply it on the instance of StatefulBeanToCsv

Here is the sample code

dependency:

<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>4.6</version>
</dependency>

Class of POJO:

import com.opencsv.bean.CsvBindByName;
import com.opencsv.bean.CsvBindByPosition;
import lombok.Data;

@Data
public class POJO {

    @CsvBindByPosition(position = 0)
    @CsvBindByName(column = "var1")
    private String var1;

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

}

Class of CustomMappingStrategy:

import com.opencsv.bean.BeanField;
import com.opencsv.bean.ColumnPositionMappingStrategy;
import com.opencsv.bean.CsvBindByName;
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

public class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {

        super.setColumnMapping(new String[ FieldUtils.getAllFields(bean.getClass()).length]);
        final int numColumns = findMaxFieldIndex();
        if (!isAnnotationDriven() || numColumns == -1) {
            return super.generateHeader(bean);
        }

        String[] header = new String[numColumns + 1];

        BeanField<T> 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> beanField) {
        if (beanField == null || beanField.getField() == null
                || beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class).length == 0) {
            return StringUtils.EMPTY;
        }

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

code of the logic to output List into a csv:

List<POJO> sourceList = new ArrayList<>();
sourceList.add(pojo1);

OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File("./test.csv"), false), Charset.forName("UTF-8"));

CustomMappingStrategy<POJO> mappingStrategy = new CustomMappingStrategy<>();
mappingStrategy.setType(POJO.class);

StatefulBeanToCsv<POJO> sbc = new StatefulBeanToCsvBuilder<POJO>(writer)
        .withSeparator(CSVWriter.DEFAULT_SEPARATOR)
        .withMappingStrategy(mappingStrategy)
        .build();

sbc.write(sourceList);
writer.flush();

Reference: https://mcmap.net/q/264980/-opencsv-how-to-create-csv-file-from-pojo-with-custom-column-headers-and-custom-column-positions

Bearnard answered 22/4, 2024 at 4:42 Comment(0)
P
-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();
            }
        };
Prelect answered 26/1, 2021 at 20:53 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.