OpenCSV:如何使用自定义列标题和自定义列位置从 POJO 创建 CSV 文件?
Posted
技术标签:
【中文标题】OpenCSV:如何使用自定义列标题和自定义列位置从 POJO 创建 CSV 文件?【英文标题】:OpenCSV: How to create CSV file from POJO with custom column headers and custom column positions? 【发布时间】:2017-12-25 12:15:30 【问题描述】:我创建了一个 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;
然后我使用 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 文件并控制列位置?
问候, 维克拉姆·帕塔尼亚
【问题讨论】:
This 示例应该对您有所帮助。 嗨 Rao,这个链接失效了 正确检查。它正在工作。 它不再工作了...... 【参考方案1】:我也遇到过类似的问题。 AFAIK 在 OpenCSV 中没有内置功能允许使用自定义列名 和 顺序将 bean 写入 CSV。
有两个主要的MappingStrategy
ies 在 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;
并在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
必须同时包含CsvBindByName
和CsvBindByPosition
注释。第一个给出标题列名称,第二个在输出 CSV 标题中创建列的顺序。现在,如果我们更改(使用注释)列名或 MappingsBean
类中的排序 - 该更改将反映在输出 CSV 文件中。
【讨论】:
其实 ColumnPositionMappingStrategy如果您没有 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();
【讨论】:
【参考方案3】:更正上述答案以匹配新版本。
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;
【讨论】:
Sebast26 答案不适用于 4.3,但确实如此。谢谢! 使用新版本更好地工作(使用 4.5) 这适用于我的 opencsv 版本 4.5,谢谢。 不适用于最新的 5.0 版本。 ColumnPositionMappingStrategy & BeanField 中没有方法 findMaxFieldIndex() 被概括为 BeanField感谢这个线程,它对我来说真的很有用......我已经对提供的解决方案进行了一些改进,以便也接受某些字段没有注释的 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】:我想实现双向导入/导出 - 能够将生成的 CSV 导入回 POJO,反之亦然。
我无法为此使用@CsvBindByPosition,因为在这种情况下 - 自动选择了 ColumnPositionMappingStrategy。每个文件:this strategy requires that the file does NOT have a header。
我用来实现目标的方法:
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);
总结:
-
无论列顺序如何,我们都可以将 CSV 读取到 POJO - 因为我们是
使用@CsvBindByName
我们可以控制写入时使用的列顺序
自定义比较器
【讨论】:
【参考方案6】:试试下面的方法:
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);
其中 columns 是你的 bean 的列,Employee 是你的 bean
【讨论】:
【参考方案7】:如果您只想根据成员变量在模型类中出现的顺序(本例中为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
执行以下操作:
-
获取我们数据类中的每个成员变量字段 (
CsvRow
)
查找所有带有@CsvBindByName
注释的对象(按照您在CsvRow
模型中指定的顺序)
每个大写都匹配默认的 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",...
这种方法的好处是它无需对任何列名进行硬编码,如果您需要添加/删除列,这将大大简化事情。
【讨论】:
【参考方案8】:我在使用最新版本的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
)
【讨论】:
【参考方案9】:通用类的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);
【讨论】:
【参考方案10】: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();
结果:
column_c |列_b | column_a
【讨论】:
【参考方案11】:在最新版本中@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;
【讨论】:
【参考方案12】:我认为处理标题列顺序的预期和最灵活的方法是通过 HeaderColumnNameMappinStrategy.setColumnOrderOnWrite() 注入一个比较器。
对我来说,最直观的方法是按照在 CsvBean 中指定的顺序编写 CSV 列,但您也可以调整比较器以在指定顺序的位置使用您自己的注释。然后不要忘记重命名 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);
【讨论】:
【参考方案13】:很棒的线程,我的 pojo 中没有任何注释,这就是我根据之前的所有答案所做的。希望对其他人有所帮助。
OpenCsv 版本:5.0 列出 readVendors = getFromMethod(); String[] 字段= "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);
//许多用户在线程中提到的自定义映射类 私有静态类 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】:以下解决方案适用于 opencsv 5.0。
首先,您需要继承 ColumnPositionMappingStrategy
类并重写 generateHeader
方法来创建自定义标头,以便同时使用 CsvBindByName 和 CsvBindByPosition 注释,如下所示。
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;
下一步是在将 bean 写入 CSV 时使用此映射策略,如下所示。
CustomMappingStrategy<ScanReport> strategy = new CustomMappingStrategy<>();
strategy.setType(ScanReport.class);
// Write a bean to csv file.
StatefulBeanToCsv<ScanReport> beanToCsv = new StatefulBeanToCsvBuilder<ScanReport>(writer)
.withMappingStrategy(strategy).build();
beanToCsv.write(beanList);
【讨论】:
【参考方案15】:以下内容适用于我将 POJO 映射到具有自定义列定位和自定义列标题的 CSV 文件(使用 opencsv-5.0 测试):
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();
波乔
生成的 CSV 文件中的列定位:
生成的 CSV 文件中的列定位将根据注释@CsvBindByPosition
生成的 CSV 文件中的标题名称:
如果该字段有@CsvBindByName
,则生成的标题将按照annonation
如果字段没有@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);
【讨论】:
这仍然适用于 OpenCSV 5.2。由于 OpenCSV 的向后兼容性差,大多数提出的其他解决方案甚至无法针对 5.2 进行编译。 我试过这个,但是当我从 csv 读取时,我在 csvbindbyname 和 customcsvbindbyname(带转换器)中设置的元素值没有被应用。 适用于 opencsv 5.4。顺便说一句,有没有办法告诉opencsv:“这是我想要前两列的顺序,但其余的,按照你想要的任何顺序放置,但确保你全部使用它们”。我只关心要先排序的几列,不想记住注释所有其他 bean 字段。 @VahidPazirandeh 在这种自定义情况下,我会使用“opencsv”来解析标题行和数据行。从那时起,我将手动将各个列映射到我的对象字段。我不确定“opencsv”是否提供了这种级别的定制。即使是这样,我相信它会使代码比手动执行复杂得多。【参考方案16】: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;
【讨论】:
【参考方案17】: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();
希望对大家有所帮助
【讨论】:
太棒了!这应该直接进入源代码。我所做的唯一更改是让您的位置从零开始。【参考方案18】:这也可以使用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();
【讨论】:
【参考方案19】:这是添加对基于@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);
【讨论】:
【参考方案20】:如果您需要保留原始 CSV 的列顺序:使用 HeaderColumnNameMappingStrategy 进行读取,然后使用相同的策略进行写入。在这种情况下,“相同”不仅意味着同一个类,而且实际上是同一个对象。
来自StatefulBeanToCsvBuilder.withMappingStrategy
的javadoc:
读取 CSV 源代码是完全合法的,请使用映射 读取操作中的策略,并将其传递给此方法以获取 写操作。这节省了一些处理时间,但是,更多 重要的是,保留标题顺序。
这样您将获得一个包含标题的 CSV,其中列的顺序与原始 CSV 相同。
使用 OpenCSV 5.4 为我工作。
【讨论】:
以上是关于OpenCSV:如何使用自定义列标题和自定义列位置从 POJO 创建 CSV 文件?的主要内容,如果未能解决你的问题,请参考以下文章
如何对返回 orm 对象和自定义列的元组的查询进行正确排序、分组?