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

时间:2017-07-20 02:01:54

标签: 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;
}

然后我使用StatefulBeanToCsv类写入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();

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

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

此致 维克拉姆帕坦尼亚

20 个答案:

答案 0 :(得分:19)

我遇到过类似的问题。 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()

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

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

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

答案 1 :(得分:12)

上面的答案已更正,以与新版本匹配。

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

我的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;

答案 2 :(得分:5)

以下对我有用的方法将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();
    }
}

Pojo

生成的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;
    }
    

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

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

答案 3 :(得分:2)

我希望获得与主题启动器相同的结果,但我也希望能够将生成的CSV导入回POJO。没有提出的解决方案帮助我实现目标。

要获得我的结果,我需要拒绝使用@CsvBindByPosition,因为在这种情况下-ColumnPositionMappingStrategy是自动选择的。每个文档:this strategy requires that the file does NOT have a header

但是,如果我们使用带有列名绑定的openCSV进行编写-我们会自动添加一个标头(可以删除它,但我发现它很有用)。

我用来实现目标的东西:

HeaderColumnNameMappingStrategy
mappingStrategy.setColumnOrderOnWrite(Comparator<String> writeOrder)

用于读取或写入csv的CsvUtils

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

用于导入/导出的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
}

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

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

订购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);
}

将导出的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);
}

总结:

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

答案 4 :(得分:1)

感谢这个线程,它对我真的很有用...我对所提供的解决方案进行了一些增强,以便也接受一些未注释(不打算读/写)字段的POJO:

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

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

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

    String[] header = new String[numColumns];

    BeanField<T> beanField;
    for (int i = 0; i <= totalFieldNum; i++) {
        beanField = findField(i);
        if (isFieldAnnotated(beanField.getField())) {
            String columnHeaderName = extractHeaderName(beanField);
            header[i] = columnHeaderName;
        }
    }
    return header;
}

private int getAnnotatedFields(T bean) {
    return (int) Arrays.stream(FieldUtils.getAllFields(bean.getClass()))
            .filter(this::isFieldAnnotated)
            .count();
}

private boolean isFieldAnnotated(Field f) {
    return f.isAnnotationPresent(CsvBindByName.class) || f.isAnnotationPresent(CsvCustomBindByName.class);
}

private String extractHeaderName(final BeanField beanField) {
    if (beanField == null || beanField.getField() == null) {
        return StringUtils.EMPTY;
    }

    Field field = beanField.getField();

    if (field.getDeclaredAnnotationsByType(CsvBindByName.class).length != 0) {
        final CsvBindByName bindByNameAnnotation = field.getDeclaredAnnotationsByType(CsvBindByName.class)[0];
        return bindByNameAnnotation.column();
    }

    if (field.getDeclaredAnnotationsByType(CsvCustomBindByName.class).length != 0) {
        final CsvCustomBindByName bindByNameAnnotation = field.getDeclaredAnnotationsByType(CsvCustomBindByName.class)[0];
        return bindByNameAnnotation.column();
    }

    return StringUtils.EMPTY;
}

}

答案 5 :(得分:1)

在使用最新版本的opencsv(4.6)的同时,删除了所有对不赞成使用的API的引用,从而改善了以前的答案。

通用Kotlin解决方案

/**
 * Custom OpenCSV [ColumnPositionMappingStrategy] that allows for a header line to be generated from a target CSV
 * bean model class using the following annotations when present:
 * * [CsvBindByName]
 * * [CsvCustomBindByName]
 */
class CustomMappingStrategy<T>(private val beanType: Class<T>) : ColumnPositionMappingStrategy<T>() {
    init {
        setType(beanType)
        setColumnMapping(*getAnnotatedFields().map { it.extractHeaderName() }.toTypedArray())
    }

    override fun generateHeader(bean: T): Array<String> = columnMapping

    private fun getAnnotatedFields() = beanType.declaredFields.filter { it.isAnnotatedByName() }.toList()

    private fun Field.isAnnotatedByName() = isAnnotationPresent(CsvBindByName::class.java) || isAnnotationPresent(CsvCustomBindByName::class.java)

    private fun Field.extractHeaderName() =
        getAnnotation(CsvBindByName::class.java)?.column ?: getAnnotation(CsvCustomBindByName::class.java)?.column ?: EMPTY
}

然后按以下方式使用它:

private fun csvBuilder(writer: Writer) =
    StatefulBeanToCsvBuilder<MappingsBean>(writer)
        .withSeparator(ICSVWriter.DEFAULT_SEPARATOR)
        .withMappingStrategy(CustomMappingStrategy(MappingsBean::class.java))
        .withApplyQuotesToAll(false)
        .build()

// Kotlin try-with-resources construct
PrintWriter(File("$reportOutputDir/$REPORT_FILENAME")).use { writer ->
    csvBuilder(writer).write(makeFinalMappingBeanList())
}

为了完整起见,这是CSV bean作为Kotlin数据类:

data class MappingsBean(
    @field:CsvBindByName(column = "TradeID")
    @field:CsvBindByPosition(position = 0, required = true)
    private val tradeId: String,

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

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

    @field:CsvBindByName(column = "GWML File")
    @field:CsvBindByPosition(position = 3)
    private val gwmlFile: String? = null,

    @field:CsvBindByName(column = "MxML File")
    @field:CsvBindByPosition(position = 4)
    private val mxmlFile: String? = null,

    @field:CsvBindByName(column = "MxML Counterparty")
    @field:CsvBindByPosition(position = 5)
    private val mxmlCounterParty: String? = null,

    @field:CsvBindByName(column = "GWML Counterparty")
    @field:CsvBindByPosition(position = 6)
    private val gwmlCounterParty: String? = null
)

答案 6 :(得分:1)

还有一个5.2版本的版本,因为我在尝试上述答案时遇到@CsvCustomBindByName批注。

我定义了自定义注释:

@Target(ElementType.FIELD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface CsvPosition {

  int position();
}

和自定义映射策略

public class CustomMappingStrategy<T> extends HeaderColumnNameMappingStrategy<T> {

  private final Field[] fields;

  public CustomMappingStrategy(Class<T> clazz) {
    fields = clazz.getDeclaredFields();
    Arrays.sort(fields, (f1, f2) -> {
      CsvPosition position1 = f1.getAnnotation(CsvPosition.class);
      CsvPosition position2 = f2.getAnnotation(CsvPosition.class);
      return Integer.compare(position1.position(), position2.position());
    });
  }

  @Override
  public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
    String[] header = new String[fields.length];
    for (Field f : fields) {
      CsvPosition position = f.getAnnotation(CsvPosition.class);
      header[position.position() - 1] = getName(f);
    }
    headerIndex.initializeHeaderIndex(header);
    return header;
  }

  private String getName(Field f) {
    CsvBindByName csvBindByName = f.getAnnotation(CsvBindByName.class);
    CsvCustomBindByName csvCustomBindByName = f.getAnnotation(CsvCustomBindByName.class);
    return csvCustomBindByName != null
      ? csvCustomBindByName.column() == null || csvCustomBindByName.column().isEmpty() ? f.getName() : csvCustomBindByName.column()
      : csvBindByName.column() == null || csvBindByName.column().isEmpty() ? f.getName() : csvBindByName.column();
  }

}

我的POJO bean是这样注释的

public class Record {

  @CsvBindByName(required = true)
  @CsvPosition(position = 1)
  Long id;
  @CsvCustomBindByName(required = true, converter = BoolanCSVField.class)
  @CsvPosition(position = 2)
  Boolean deleted;
  ...
}

以及编写者的最终代码:

CustomMappingStrategy<Record> mappingStrategy = new CustomMappingStrategy<>(Record.class);
mappingStrategy.setType(Record.class);
StatefulBeanToCsv beanToCsv = new StatefulBeanToCsvBuilder(writer)
.withApplyQuotesToAll(false)
.withOrderedResults(true)
.withMappingStrategy(mappingStrategy)
.build();

我希望这对某人有帮助

答案 7 :(得分:1)

在最新版本中,@ 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();
    }
}

模型如下:

@CsvBindByName(column = "id")
@CsvBindByPosition(position = 0)
private Long id;
@CsvBindByName(column = "name")
@CsvBindByPosition(position = 1)
private String name;

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

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

答案 8 :(得分:1)

以下解决方案适用于opencsv 5.0。

首先,您需要继承.container:last-child { margin-bottom: 0px; } 类并重写ColumnPositionMappingStrategy方法来创建自定义标头,以同时使用CsvBindByName和CsvBindByPosition批注,如下所示。

generateHeader

下一步是在将bean写入CSV时使用此映射策略,如下所示。

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

/**
 * @param <T>
 */
class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
    /*
     * (non-Javadoc)
     * 
     * @see com.opencsv.bean.ColumnPositionMappingStrategy#generateHeader(java.lang.
     * Object)
     */
    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        final int numColumns = getFieldMap().values().size();
        if (numColumns == -1) {
            return super.generateHeader(bean);
        }

        String[] header = new String[numColumns];
        super.setColumnMapping(header);

        BeanField<T, Integer> beanField;
        for (int i = 0; i < numColumns; i++) {
            beanField = findField(i);
            String columnHeaderName = beanField.getField().getDeclaredAnnotation(CsvBindByName.class).column();
            header[i] = columnHeaderName;
        }
        return header;
    }
}

答案 9 :(得分:0)

如果您需要这样做以保留原始 CSV 中的列顺序:使用 HeaderColumnNameMappingStrategy 进行读取,然后使用相同的写入策略。在这种情况下,“相同”不仅意味着同一个类,而且实际上是同一个对象。

来自 StatefulBeanToCsvBuilder.withMappingStrategy 的 javadoc:

<块引用>

读取 CSV 源是完全合法的,采用映射 读取操作中的策略,并将其传递给此方法以进行 写操作。这节省了一些处理时间,但是,更多 重要的是,保留标题顺序

这样,您将获得包含标题的 CSV,列的顺序与原始 CSV 的顺序相同。

使用 OpenCSV 5.4 为我工作。

答案 10 :(得分:0)

这是用于将对基于@CsvBindByPosition的排序的支持添加到默认HeaderColumnNameMappingStrategy的代码。已测试最新版本5.2

方法是存储2张地图。第一个headerPositionMap用于存储位置元素,因此可以使用setColumnOrderOnWrite,第二个columnMap可以从中查找实际的列名,而不用大写

public class HeaderColumnNameWithPositionMappingStrategy<T> extends HeaderColumnNameMappingStrategy<T> {

    protected Map<String, String> columnMap;

    @Override
    public void setType(Class<? extends T> type) throws CsvBadConverterException {
        super.setType(type);
        columnMap = new HashMap<>(this.getFieldMap().values().size());
        Map<String, Integer> headerPositionMap = new HashMap<>(this.getFieldMap().values().size());
        for (Field field : type.getDeclaredFields()) {
            if (field.isAnnotationPresent(CsvBindByPosition.class) && field.isAnnotationPresent(CsvBindByName.class)) {
                int position = field.getAnnotation(CsvBindByPosition.class).position();
                String colName = "".equals(field.getAnnotation(CsvBindByName.class).column()) ? field.getName() : field.getAnnotation(CsvBindByName.class).column();
                headerPositionMap.put(colName.toUpperCase().trim(), position);
                columnMap.put(colName.toUpperCase().trim(), colName);
            }
        }
        super.setColumnOrderOnWrite((String o1, String o2) -> {
            if (!headerPositionMap.containsKey(o1) || !headerPositionMap.containsKey(o2)) {
                return 0;
            }
            return headerPositionMap.get(o1) - headerPositionMap.get(o2);
        });
    }

    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        String[] headersRaw = super.generateHeader(bean);
        return Arrays.stream(headersRaw).map(h -> columnMap.get(h)).toArray(String[]::new);
    }
}

答案 11 :(得分:0)

这也可以使用HeaderColumnNameMappingStrategy和自定义Comparator来完成。 由官方文档http://opencsv.sourceforge.net/#mapping_strategies

推荐
    File reportFile = new File(reportOutputDir + "/" + REPORT_FILENAME);
    Writer writer = new PrintWriter(reportFile);
    
final List<String> order = List.of("TradeID", "GWML GUID", "MXML GUID", "GWML File", "MxML File", "MxML Counterparty", "GWML Counterparty");
    final FixedOrderComparator comparator = new FixedOrderComparator(order);
    HeaderColumnNameMappingStrategy<MappingsBean> strategy = new HeaderColumnNameMappingStrategy<>();
    strategy.setType(MappingsBean.class);
    strategy.setColumnOrderOnWrite(comparator);

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

答案 12 :(得分:0)

sebast26的第一个解决方案对我有用,但对于opencsv 5.2版,它需要对CustomMappingStrategy类进行一些更改:

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() {
    super.generateHeader(bean); // without this the file contains ONLY headers
    return HEADER;
}

}

答案 13 :(得分:0)

很棒的主题,我的pojo中没有任何注释,这是我根据之前所有答案所做的。希望对别人有帮助。

OpenCsv版本:5.0 列出readVendors = getFromMethod(); String [] fields = {“ id”,“ recordNumber”,“ finVendorIdTb”,“ finVenTechIdTb”,“ finShortNameTb”,“ finVenName1Tb”,“ finVenName2Tb”};

            String[] csvHeader= {"Id#","Shiv Record Number","Shiv Vendor Id","Shiva Tech Id#","finShortNameTb","finVenName1Tb","finVenName2Tb"};

            CustomMappingStrategy<FinVendor> mappingStrategy = new CustomMappingStrategy(csvHeader);//csvHeader as per custom header irrespective of pojo field name
            mappingStrategy.setType(FinVendor.class);
            mappingStrategy.setColumnMapping(fields);//pojo mapping fields


            StatefulBeanToCsv<FinVendor> beanToCsv = new StatefulBeanToCsvBuilder<FinVendor>(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER).withMappingStrategy(mappingStrategy).build();
            beanToCsv.write(readVendors);
许多用户在线程中提到的

// custom映射类 私有静态类CustomMappingStrategy扩展了ColumnPositionMappingStrategy {

                    String[] header;

                    public CustomMappingStrategy(String[] cols) {

                            header = cols;
                    }

                    @Override
                    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
                        super.generateHeader(bean);
                            return header;
                    }
                    }

输出:

Id#     Shiv Record Number      Shiv Vendor Id   Fin Tech Id#      finShortNameTb  finVenName1Tb   finVenName2Tb   finVenDefaultLocTb
1       VEN00053                678             33316025986        THE ssOHIO S_2  THE UNIVERSITY     CHK         Test
2       VEN02277                1217            3044374205         Fe3 MECHA_1     FR3INC             EFT-1
3       VEN03118                1310            30234484121        PE333PECTUS_1   PER332CTUS AR      EFT-1       Test

答案 14 :(得分:0)

我认为处理标题列顺序的最灵活的方法是通过HeaderColumnNameMappinStrategy.setColumnOrderOnWrite()注入比较器。

对我来说,最直观的方法是按照与CsvBean中指定的顺序写入CSV列,但是您也可以调整Comparator以在指定顺序的地方使用自己的注释。不要忘了重命名Comparator类;)

集成:

HeaderColumnNameMappingStrategy<MyCsvBean> mappingStrategy = new HeaderColumnNameMappingStrategy<>();
    mappingStrategy.setType(MyCsvBean.class);
    mappingStrategy.setColumnOrderOnWrite(new ClassFieldOrderComparator(MyCsvBean.class));

比较器:

private class ClassFieldOrderComparator implements Comparator<String> {

    List<String> fieldNamesInOrderWithinClass;

    public ClassFieldOrderComparator(Class<?> clazz) {
        fieldNamesInOrderWithinClass = Arrays.stream(clazz.getDeclaredFields())
                .filter(field -> field.getAnnotation(CsvBindByName.class) != null)
              // Handle order by your custom annotation here
              //.sorted((field1, field2) -> {
              //   int field1Order = field1.getAnnotation(YourCustomOrderAnnotation.class).getOrder();
              //   int field2Order = field2.getAnnotation(YourCustomOrderAnnotation.class).getOrder();
              //   return Integer.compare(field1Order, field2Order);
              //})
                .map(field -> field.getName().toUpperCase())
                .collect(Collectors.toList());
    }

    @Override
    public int compare(String o1, String o2) {
        int fieldIndexo1 = fieldNamesInOrderWithinClass.indexOf(o1);
        int fieldIndexo2 = fieldNamesInOrderWithinClass.indexOf(o2);
        return Integer.compare(fieldIndexo1, fieldIndexo2);
    }
}

答案 15 :(得分:0)

这是版本高于4.3的解决方案:

public class MappingBean {
     @CsvBindByName(column = "column_a")
     private String columnA;
     @CsvBindByName(column = "column_b")
     private String columnB;
     @CsvBindByName(column = "column_c")
     private String columnC;

     // getters and setters
}

并以它为例:

import org.apache.commons.collections4.comparators.FixedOrderComparator;

...

var mappingStrategy = new HeaderColumnNameMappingStrategy<MappingBean>();
mappingStrategy.setType(MappingBean.class);        
mappingStrategy.setColumnOrderOnWrite(new FixedOrderComparator<>("COLUMN_C", "COLUMN_B", "COLUMN_A"));

var sbc = new StatefulBeanToCsvBuilder<MappingBean>(writer)
      .withMappingStrategy(mappingStrategy)
      .build();

sbc.write(accountsSettings);

答案 16 :(得分:0)

通用类的CustomMappingStrategy。

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

POJO类

 public class Customer{

     @CsvBindByPosition(position=1)
     @CsvBindByName(column="CUSTOMER", required = true)
     private String customer;
}

客户端类

 List<T> data = getEmployeeRecord();
CustomMappingStrategy custom = new CustomMappingStrategy();
custom.setType(Employee.class);
StatefulBeanToCsv<T> writer = new StatefulBeanToCsvBuilder<T>(response.getWriter())
                .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
                .withSeparator('|')
                .withOrderedResults(false)
                .withMappingStrategy(custom)
                .build();
        writer.write(reportData);

答案 17 :(得分:0)

如果您只想根据成员变量在模型类中的显示顺序(在此示例中为CsvRow行)对CSV列进行排序,则可以使用Comparator实现以相当简单的方式解决这个问题。这是在Kotlin中执行此操作的示例:

class ByMemberOrderCsvComparator : Comparator<String> {

    private val memberOrder by lazy {
        FieldUtils.getAllFields(CsvRow::class.java)
                .map { it.getDeclaredAnnotation(CsvBindByName::class.java) }
                .map { it?.column ?: "" }
                .map { it.toUpperCase(Locale.US) } // OpenCSV UpperCases all headers, so we do this to match
    }

    override fun compare(field1: String?, field2: String?): Int {
        return memberOrder.indexOf(field1) - memberOrder.indexOf(field2)
    }

}

Comparator执行以下操作:

  1. 获取数据类(CsvRow)中的每个成员变量字段
  2. 找到所有带有@CsvBindByName注释的对象(按照您在CsvRow模型中指定的顺序)
  3. 每个大写字母都与默认的OpenCsv实现匹配

接下来,将此Comparator应用于您的MappingStrategy,这样它将根据指定的顺序进行排序:

val mappingStrategy = HeaderColumnNameMappingStrategy<OrderSummaryCsvRow>()
mappingStrategy.setColumnOrderOnWrite(ByMemberOrderCsvComparator())
mappingStrategy.type = CsvRow::class.java
mappingStrategy.setErrorLocale(Locale.US)

val csvWriter = StatefulBeanToCsvBuilder<OrderSummaryCsvRow>(writer)
                    .withMappingStrategy(mappingStrategy)
                    .build()

作为参考,这是一个示例CsvRow类(您可以根据自己的需要将其替换为自己的模型):

data class CsvRow(
    @CsvBindByName(column = "Column 1")
    val column1: String,

    @CsvBindByName(column = "Column 2")
    val column2: String,

    @CsvBindByName(column = "Column 3")
    val column3: String,

    // Other columns here ...
)

哪个会产生如下的CSV文件:

"COLUMN 1","COLUMN 2","COLUMN 3",...
"value 1a","value 2a","value 3a",...
"value 1b","value 2b","value 3b",...

这种方法的好处在于,它无需对任何列名进行硬编码,这在需要添加/删除列的情况下将大大简化事情。

答案 18 :(得分:0)

尝试如下操作:

private static class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {

    String[] header;

    public CustomMappingStrategy(String[] cols) {
        header = cols;
    }

    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        return header;
    }
}

然后按以下方式使用它:

String[] columns = new String[]{"Name", "Age", "Company", "Salary"};
        CustomMappingStrategy<Employee> mappingStrategy = new CustomMappingStrategy<Employee>(columns);

其中column是您的bean的列,而Employee是您的bean

答案 19 :(得分:0)

如果您没有getDeclaredAnnotationsByType方法,但需要原始字段名称的名称:

beanField.getField()。的getName()

public 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().getDeclaredAnnotations().length == 0) {
        return StringUtils.EMPTY;
    }
    return beanField.getField().getName();
}

}