OpenCSV:如何从POJO创建具有自定义列标题和自定义列位置的CSV文件?

2022-08-31 15:09:54

我创建了一个 MappingsBean 类,其中指定了 CSV 文件的所有列。接下来,我解析 XML 文件并创建映射 bean 列表。然后,我将该数据作为报告写入CSV文件。

我使用以下注释:

public class MappingsBean {

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

    @CsvBindByName(column = "GWML GUID", required = true)
    @CsvBindByPosition(position = 1)
    private String gwmlGUID;

    @CsvBindByName(column = "MXML GUID", required = true)
    @CsvBindByPosition(position = 2)
    private String mxmlGUID;

    @CsvBindByName(column = "GWML File")
    @CsvBindByPosition(position = 3)
    private String gwmlFile;

    @CsvBindByName(column = "MxML File")
    @CsvBindByPosition(position = 4)
    private String mxmlFile;

    @CsvBindByName(column = "MxML Counterparty")
    @CsvBindByPosition(position = 5)
    private String mxmlCounterParty;

    @CsvBindByName(column = "GWML Counterparty")
    @CsvBindByPosition(position = 6)
    private String gwmlCounterParty;
}

然后我使用类写入CSV文件:StatefulBeanToCsv

File reportFile = new File(reportOutputDir + "/" + REPORT_FILENAME);
Writer writer = new PrintWriter(reportFile);
StatefulBeanToCsv<MappingsBean> beanToCsv = new 
                              StatefulBeanToCsvBuilder(writer).build();
beanToCsv.write(makeFinalMappingBeanList());
writer.close();

这种方法的问题在于,如果我用于控制位置,那么我无法生成列名。如果我使用,那么我无法设置列的位置。@CsvBindByPosition(position = 0)@CsvBindByName(column = "TradeID")

有没有办法同时使用这两个批注,以便我可以创建带有列标题的 CSV 文件并控制列位置?

问候, 维克拉姆·帕塔尼亚


答案 1

我也有类似的问题。AFAIK OpenCSV中没有内置功能,允许使用自定义列名顺序将Bean写入CSV。

OpenCSV中有两种开箱即用的主要功能:MappingStrategy

  • HeaderColumnNameMappingStrategy:允许根据自定义名称将 CVS 文件列映射到 Bean 字段;将bean写入CSV时,这允许更改列标题名称,但我们无法控制列顺序
  • ColumnPositionMappingStrategy:允许根据列排序将CSV文件列映射到bean字段;当将bean写入CSV时,我们可以控制列顺序,但我们得到一个空标题(实现作为标题返回)new String[0]

我发现实现自定义列名称和排序的唯一方法是编写自定义 。MappingStrategy

第一个解决方案:快速简便,但硬编码

创建自定义 :MappingStrategy

class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
    private static final String[] HEADER = new String[]{"TradeID", "GWML GUID", "MXML GUID", "GWML File", "MxML File", "MxML Counterparty", "GWML Counterparty"};

    @Override
    public String[] generateHeader() {
        return HEADER;
    }
}

并将其用于:StatefulBeanToCsvBuilder

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

final StatefulBeanToCsv<MappingsBean> beanToCsv = new StatefulBeanToCsvBuilder<MappingsBean>(writer)
    .withMappingStrategy(mappingStrategy)
    .build();
beanToCsv.write(makeFinalMappingBeanList());
writer.close()

在课堂上,我们留下了注释 - 控制排序(在这个解决方案中,不需要注释)。由于自定义映射策略,标题列名称包含在生成的CSV文件中。MappingsBeanCsvBindByPositionCsvBindByName

此解决方案的缺点是,当我们通过注释更改列顺序时,我们必须手动更改自定义映射策略中的常量。CsvBindByPositionHEADER

第二种解决方案:更灵活

第一个解决方案有效,但对我来说并不好。基于的内置实现,我想出了另一个实现:MappingStrategy

class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
    @Override
    public String[] generateHeader() {
        final int numColumns = findMaxFieldIndex();
        if (!isAnnotationDriven() || numColumns == -1) {
            return super.generateHeader();
        }

        header = new String[numColumns + 1];

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

您可以在与第一个解决方案完全相同的情况下使用此自定义策略(请记住调用 ,否则此解决方案将不起作用)。StatefulBeanToCsvBuildermappingStrategy.setType(MappingsBean.class);

目前,我们必须同时包含注释和注释。第一个用于提供标题列名称,第二个用于在输出 CSV 标头中创建列的排序。现在,如果我们更改(使用注释)列名或类中的顺序 - 该更改将反映在输出CSV文件中。MappingsBeanCsvBindByNameCsvBindByPositionMappingsBean


答案 2

更正了上面的答案以匹配较新版本。

package csvpojo;

import org.apache.commons.lang3.StringUtils;

import com.opencsv.bean.BeanField;
import com.opencsv.bean.ColumnPositionMappingStrategy;
import com.opencsv.bean.CsvBindByName;
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;

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

然后调用它来生成 CSV。我使用访问者作为我的POJO来填充,并在必要时进行更新。

        CustomMappingStrategy<Visitors> mappingStrategy = new CustomMappingStrategy<>();
        mappingStrategy.setType(Visitors.class);
        // writing sample
        List<Visitors> beans2 = new ArrayList<Visitors>();

        Visitors v = new Visitors();
        v.set_1_firstName(" test1");
        v.set_2_lastName("lastname1");
        v.set_3_visitsToWebsite("876");
        beans2.add(v);

        v = new Visitors();
        v.set_1_firstName(" firstsample2");
        v.set_2_lastName("lastname2");
        v.set_3_visitsToWebsite("777");
        beans2.add(v);

        Writer writer = new FileWriter("G://output.csv");
        StatefulBeanToCsv<Visitors> beanToCsv = new StatefulBeanToCsvBuilder<Visitors>(writer)
                .withMappingStrategy(mappingStrategy).withSeparator(',').withApplyQuotesToAll(false).build();
        beanToCsv.write(beans2);
        writer.close();

我的豆类注释看起来像这样

 @CsvBindByName (column = "First Name", required = true)
 @CsvBindByPosition(position=1)
 private String firstName;


 @CsvBindByName (column = "Last Name", required = true)
 @CsvBindByPosition(position=0)
 private String lastName;