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

Vik*_*nia 29 java xml csv opencsv

我创建了一个MappingsBean类,其中指定了CSV文件的所有列.接下来,我解析XML文件并创建mappingbeans列表.然后我将该数据写入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;
}
Run Code Online (Sandbox Code Playgroud)

然后我用StatefulBeanToCsvclass写入CSV文件:

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

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

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

此致,Vikram Pathania

seb*_*t26 22

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

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

  • 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并用于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()
Run Code Online (Sandbox Code Playgroud)

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

这个解决方案的缺点是,当我们通过CsvBindByPosition注释更改列排序时,我们必须HEADER在自定义映射策略中手动更改常量.

第二种解决方案:更灵活

第一个解决方案有效,但对我来说并不好.基于内置实现,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();
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用StatefulBeanToCsvBuilder与第一个解决方案完全相同的自定义策略(请记住调用mappingStrategy.setType(MappingsBean.class);,否则此解决方案将无效).

目前我们MappingsBean必须包含CsvBindByNameCsvBindByPosition注释.第一个给出标题列名称,第二个用于在输出CSV标题中创建列的排序.现在,如果我们更改(使用注释)列名或MappingsBean类中的排序- 该更改将反映在输出CSV文件中.

  • 第一个解决方案不适用于 4.5 版。为了使它工作添加 super.generateHeader(bean); 作为覆盖的 generateHeader(T bean) 中的第一行 (4认同)

Lal*_*era 17

更正了上述答案以匹配较新版本.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后调用它来生成CSV.我使用Visitors作为我的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();
Run Code Online (Sandbox Code Playgroud)

我的bean注释看起来像这样

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


 @CsvBindByName (column = "Last Name", required = true)
 @CsvBindByPosition(position=0)
 private String lastName;
Run Code Online (Sandbox Code Playgroud)

  • 不适用于最新的 5.0 版本。ColumnPositionMappingStrategy 中没有 findMaxFieldIndex() 方法,并且 BeanField 被泛化为 BeanField&lt;T, I&gt; (8认同)
  • 与新版本配合得更好(使用 4.5) (2认同)

Ant*_*pan 11

我想实现双向导入/导出 - 能够将生成的 CSV 导入回 POJO,反之亦然。

我无法为此使用 @CsvBindByPosition,因为在这种情况下 - ColumnPositionMappingStrategy 被自动选择。每个文档:此策略要求文件没有 header

我用来实现目标的方法:

HeaderColumnNameMappingStrategy
mappingStrategy.setColumnOrderOnWrite(Comparator<String> writeOrder)
Run Code Online (Sandbox Code Playgroud)

CsvUtils 读取/写入 csv

import com.opencsv.CSVWriter;
import com.opencsv.bean.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.List;

public class CsvUtils {
    private CsvUtils() {
    }

    public static <T> String convertToCsv(List<T> entitiesList, MappingStrategy<T> mappingStrategy) throws Exception {
        try (Writer writer = new StringWriter()) {
            StatefulBeanToCsv<T> beanToCsv = new StatefulBeanToCsvBuilder<T>(writer)
                    .withMappingStrategy(mappingStrategy)
                    .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
                    .build();
            beanToCsv.write(entitiesList);
            return writer.toString();
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> List<T> convertFromCsv(MultipartFile file, Class clazz) throws IOException {
        try (Reader reader = new BufferedReader(new InputStreamReader(file.getInputStream()))) {
            CsvToBean<T> csvToBean = new CsvToBeanBuilder<T>(reader).withType(clazz).build();
            return csvToBean.parse();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用于导入/导出的 POJO

public class LocalBusinessTrainingPairDTO {
    //this is used for CSV columns ordering on exporting LocalBusinessTrainingPairs
    public static final String[] FIELDS_ORDER = {"leftId", "leftName", "rightId", "rightName"};

    @CsvBindByName(column = "leftId")
    private int leftId;

    @CsvBindByName(column = "leftName")
    private String leftName;

    @CsvBindByName(column = "rightId")
    private int rightId;

    @CsvBindByName(column = "rightName")
    private String rightName;
    // getters/setters omitted, do not forget to add them
}
Run Code Online (Sandbox Code Playgroud)

用于预定义字符串排序的自定义比较器:

public class OrderedComparatorIgnoringCase implements Comparator<String> {
    private List<String> predefinedOrder;

    public OrderedComparatorIgnoringCase(String[] predefinedOrder) {
        this.predefinedOrder = new ArrayList<>();
        for (String item : predefinedOrder) {
            this.predefinedOrder.add(item.toLowerCase());
        }
    }

    @Override
    public int compare(String o1, String o2) {
        return predefinedOrder.indexOf(o1.toLowerCase()) - predefinedOrder.indexOf(o2.toLowerCase());
    }
}
Run Code Online (Sandbox Code Playgroud)

POJO 的有序写作(对初始问题的回答)

public static void main(String[] args) throws Exception {
     List<LocalBusinessTrainingPairDTO> localBusinessTrainingPairsDTO = new ArrayList<>();
     LocalBusinessTrainingPairDTO localBusinessTrainingPairDTO = new LocalBusinessTrainingPairDTO();
     localBusinessTrainingPairDTO.setLeftId(1);
     localBusinessTrainingPairDTO.setLeftName("leftName");
     localBusinessTrainingPairDTO.setRightId(2);
     localBusinessTrainingPairDTO.setRightName("rightName");

     localBusinessTrainingPairsDTO.add(localBusinessTrainingPairDTO);

     //Creating HeaderColumnNameMappingStrategy
     HeaderColumnNameMappingStrategy<LocalBusinessTrainingPairDTO> mappingStrategy = new HeaderColumnNameMappingStrategy<>();
     mappingStrategy.setType(LocalBusinessTrainingPairDTO.class);
     //Setting predefined order using String comparator
     mappingStrategy.setColumnOrderOnWrite(new OrderedComparatorIgnoringCase(LocalBusinessTrainingPairDTO.FIELDS_ORDER));
     String csv = convertToCsv(localBusinessTrainingPairsDTO, mappingStrategy);
     System.out.println(csv);
}
Run Code Online (Sandbox Code Playgroud)

将导出的 CSV 读回 POJO(原始答案的补充)

重要提示:CSV 可以是无序的,因为我们仍在使用按名称绑定:

public static void main(String[] args) throws Exception {
    //omitted code from writing
    String csv = convertToCsv(localBusinessTrainingPairsDTO, mappingStrategy);

    //Exported CSV should be compatible for further import
    File temp = File.createTempFile("tempTrainingPairs", ".csv");
    temp.deleteOnExit();
    BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
    bw.write(csv);
    bw.close();
    MultipartFile multipartFile = new MockMultipartFile("tempTrainingPairs.csv", new FileInputStream(temp));

    List<LocalBusinessTrainingPairDTO> localBusinessTrainingPairDTOList = convertFromCsv(multipartFile, LocalBusinessTrainingPairDTO.class);
}
Run Code Online (Sandbox Code Playgroud)

总结:

  1. 无论列顺序如何,我们都可以将 CSV 读取到 POJO - 因为我们使用的是 @CsvBindByName
  2. 我们可以使用自定义比较器控制写入时的列顺序


dir*_*ert 9

在最新版本中,@Sebast26 的解决方案不再有效。不过基础还是很不错的。这是 v5.0 的工作解决方案

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;

class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        final int numColumns = getFieldMap().values().size();
        super.generateHeader(bean);

        String[] header = new String[numColumns];

        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();
    }
}
Run Code Online (Sandbox Code Playgroud)

该模型如下所示:

@CsvBindByName(column = "id")
@CsvBindByPosition(position = 0)
private Long id;
@CsvBindByName(column = "name")
@CsvBindByPosition(position = 1)
private String name;
Run Code Online (Sandbox Code Playgroud)

我的代助手看起来像这样:

public static <T extends AbstractCsv> String createCsv(List<T> data, Class<T> beanClazz) {
    CustomMappingStrategy<T> mappingStrategy = new CustomMappingStrategy<T>();
    mappingStrategy.setType(beanClazz);

    StringWriter writer = new StringWriter();
    String csv = "";
    try {
        StatefulBeanToCsv sbc = new StatefulBeanToCsvBuilder(writer)
                .withSeparator(';')
                .withMappingStrategy(mappingStrategy)
                .build();
        sbc.write(data);
        csv = writer.toString();
    } catch (CsvRequiredFieldEmptyException e) {
        // TODO add some logging...
    } catch (CsvDataTypeMismatchException e) {
        // TODO add some logging...
    } finally {
        try {
            writer.close();
        } catch (IOException e) {
        }
    }
    return csv;
}
Run Code Online (Sandbox Code Playgroud)


Yat*_*oel 7

以下内容适用于我将 POJO 映射到具有自定义列定位和自定义列标题(使用 opencsv-5.0 测试)的 CSV 文件:

public class CustomBeanToCSVMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {

    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {

        String[] headersAsPerFieldName = getFieldMap().generateHeader(bean); // header name based on field name

        String[] header = new String[headersAsPerFieldName.length];

        for (int i = 0; i <= headersAsPerFieldName.length - 1; i++) {

            BeanField beanField = findField(i);

            String columnHeaderName = extractHeaderName(beanField); // header name based on @CsvBindByName annotation

            if (columnHeaderName.isEmpty()) // No @CsvBindByName is present
                columnHeaderName = headersAsPerFieldName[i]; // defaults to header name based on field name

            header[i] = columnHeaderName;
        }

        headerIndex.initializeHeaderIndex(header);

        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();
    }
}
Run Code Online (Sandbox Code Playgroud)

波乔

生成的 CSV 文件中的列定位:

  • 生成的 CSV 文件中的列定位将按照注释 @CsvBindByPosition

生成的 CSV 文件中的标题名称:

  • 如果该字段具有@CsvBindByName,则生成的标头将与注释一致
  • 如果该字段没有@CsvBindByName,则生成的标头将按照字段名称

    @Getter @Setter @ToString
    public class Pojo {
    
        @CsvBindByName(column="Voucher Series") // header: "Voucher Series"
        @CsvBindByPosition(position=0)
        private String voucherSeries;
    
        @CsvBindByPosition(position=1) // header: "salePurchaseType"
        private String salePurchaseType;
    }
    
    Run Code Online (Sandbox Code Playgroud)

使用上述自定义映射策略:

CustomBeanToCSVMappingStrategy<Pojo> mappingStrategy = new CustomBeanToCSVMappingStrategy<>();
            mappingStrategy.setType(Pojo.class);

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

beanToCsv.write(pojoList);
Run Code Online (Sandbox Code Playgroud)

  • 这仍然适用于 OpenCSV 5.2。由于 OpenCSV 的向后兼容性较差,大多数提出的其他解决方案甚至无法针对 5.2 进行编译。 (3认同)