xre*_*ref 6 java opencsv supercsv
我有各种CSV,包含一些标准列和一些完全随机的字段:
firstname, lastname, dog_name, fav_hat, fav_color
bill,smith,fido,porkpie,blue
james,smith,rover,bowler,purple
firstname, lastname, car_type, floor_number
tom, collins, ford, 14
jim, jones, toyota, 120
Run Code Online (Sandbox Code Playgroud)
所以我试图将它们解析为Person.class bean,它包含firstname和lastname,然后我有一个名为PersonAttribute.class的第二个类来保存......还有其他什么.
这两个类的讽刺大纲:
class Person {
public String firstname;
public String lastname;
public List<PersonAttribute> attribs;
}
class PersonAttribute {
public Person p;
public String key; // header name, ex. 'car_type'
public String value; // column value, ex. 'ford'
}
Run Code Online (Sandbox Code Playgroud)
我一直在opencsv中使用CsvToBean函数:
public static List<Person> parseToBeans(File csvFile, HashMap<String, String> mapStrategy, Class beanClass) throws IOException {
CSVReader reader = null;
try {
reader = new CSVReader(new BufferedReader(new FileReader(csvFile)));
HeaderColumnNameTranslateMappingStrategy<Person> strategy = new HeaderColumnNameTranslateMappingStrategy<>();
strategy.setType(beanClass);
strategy.setColumnMapping(mapStrategy);
final CsvToBean<Person> csv = new CsvToBean<Person>() {
@Override
protected Object convertValue(String value, PropertyDescriptor prop) throws InstantiationException, IllegalAccessException {
value = value.trim().replaceAll(" +", " ");
return super.convertValue(value, prop);
}
};
return csv.parse(strategy, reader);
}
...
Run Code Online (Sandbox Code Playgroud)
但是,当我解析Person.class bean的csv时,我不确定如何处理创建PersonAttribute.class bean.我遇到过这篇文章,我想知道是否需要切换到supercsv来轻松处理我正在尝试做的事情?
您当然可以使用 Super CSV 实现这一点。
您可以使用
CsvBeanReader - 它不支持索引映射,所以你需要在你的 bean 中创建一个辅助方法才能使用它
CsvDozerBeanReader - 支持开箱即用的索引映射,所以会做你想做的事(需要最近发布的 Super CSV 2.1.0)
如果您不想使用 Dozer 并且能够修改您的 bean 类,最简单的选择是在您的 bean 上添加一个虚拟 setter,CsvBeanReader它将用于填充属性。我假设你Person和PersonAttributebean 有一个公共的无参数构造函数和为每个字段定义的 getter/setter(这是必需的)。
将以下虚拟 setter 添加到您的Personbean:
public void setAddAttribute(PersonAttribute attribute){
if (attribs == null){
attribs = new ArrayList<PersonAttribute>();
}
attribs.add(attribute);
}
Run Code Online (Sandbox Code Playgroud)
创建一个自定义单元格处理器,它将PersonAttribute使用来自 CSV 标题的适当键和来自 CSV 列的值填充 a 。
package org.supercsv.example;
import org.supercsv.cellprocessor.CellProcessorAdaptor;
import org.supercsv.util.CsvContext;
/**
* Creates a PersonAttribute using the corresponding header as the key.
*/
public class ParsePersonAttribute extends CellProcessorAdaptor {
private final String[] header;
public ParsePersonAttribute(final String[] header) {
this.header = header;
}
public Object execute(Object value, CsvContext context) {
if( value == null ) {
return null;
}
PersonAttribute attribute = new PersonAttribute();
// columns start at 1
attribute.setKey(header[context.getColumnNumber() - 1]);
attribute.setValue((String) value);
return attribute;
}
}
Run Code Online (Sandbox Code Playgroud)
我认为下面的例子主要是不言而喻的,但我应该指出以下几点:
我不得不使用自定义首选项,因为您的 CSV 包含不属于数据的空格
我必须动态组装字段映射和单元处理器数组,因为您的数据具有未知数量的属性(此设置通常并不复杂)
属性的所有字段映射都使用addAttribute,这对应于setAddAttribute()我们添加到您的 bean 中的方法
我使用我们的自定义单元处理器PersonAttribute为每个属性列创建了一个bean
这是代码:
package org.supercsv.example;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.CsvBeanReader;
import org.supercsv.io.ICsvBeanReader;
import org.supercsv.prefs.CsvPreference;
public class ReadWithCsvBeanReader {
private static final String CSV =
"firstname, lastname, dog_name, fav_hat, fav_color\n"
+ "bill,smith,fido,porkpie,blue\n"
+ "james,smith,rover,bowler,purple";
private static final String CSV2 =
"firstname, lastname, car_type, floor_number\n"
+ "tom, collins, ford, 14\n" + "jim, jones, toyota, 120";
// attributes start at element 2 of the header array
private static final int ATT_START_INDEX = 2;
// custom preferences required because CSV contains
spaces that aren't part of the data
private static final CsvPreference PREFS =
new CsvPreference.Builder(
CsvPreference.STANDARD_PREFERENCE)
.surroundingSpacesNeedQuotes(true).build();
public static void main(String[] args) throws IOException {
System.out.println("CsvBeanReader with first CSV input:");
readWithCsvBeanReader(new StringReader(CSV));
System.out.println("CsvBeanReader with second CSV input:");
readWithCsvBeanReader(new StringReader(CSV2));
}
private static void readWithCsvBeanReader(final Reader reader)
throws IOException {
ICsvBeanReader beanReader = null;
try {
beanReader = new CsvBeanReader(reader, PREFS);
final String[] header = beanReader.getHeader(true);
// set up the field mapping and processors dynamically
final String[] fieldMapping = new String[header.length];
final CellProcessor[] processors =
new CellProcessor[header.length];
for (int i = 0; i < header.length; i++) {
if (i < ATT_START_INDEX) {
// normal mappings
fieldMapping[i] = header[i];
processors[i] = new NotNull();
} else {
// attribute mappings
fieldMapping[i] = "addAttribute";
processors[i] =
new Optional(new ParsePersonAttribute(header));
}
}
Person person;
while ((person = beanReader.read(Person.class, fieldMapping,
processors)) != null) {
System.out.println(String.format(
"lineNo=%s, rowNo=%s, person=%s",
beanReader.getLineNumber(), beanReader.getRowNumber(),
person));
}
} finally {
if (beanReader != null) {
beanReader.close();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
输出(我toString()向你的 bean添加了方法):
CsvBeanReader with first CSV input:
lineNo=2, rowNo=2, person=Person [firstname=bill, lastname=smith, attribs=[PersonAttribute [key=dog_name, value=fido], PersonAttribute [key=fav_hat, value=porkpie], PersonAttribute [key=fav_color, value=blue]]]
lineNo=3, rowNo=3, person=Person [firstname=james, lastname=smith, attribs=[PersonAttribute [key=dog_name, value=rover], PersonAttribute [key=fav_hat, value=bowler], PersonAttribute [key=fav_color, value=purple]]]
CsvBeanReader with second CSV input:
lineNo=2, rowNo=2, person=Person [firstname=tom, lastname=collins, attribs=[PersonAttribute [key=car_type, value=ford], PersonAttribute [key=floor_number, value=14]]]
lineNo=3, rowNo=3, person=Person [firstname=jim, lastname=jones, attribs=[PersonAttribute [key=car_type, value=toyota], PersonAttribute [key=floor_number, value=120]]]
Run Code Online (Sandbox Code Playgroud)
如果你不能,或者不想修改你的 bean,那么我建议CsvDozerBeanReader在Super CSV Dozer Extension项目中使用,因为它支持嵌套和索引字段映射。查看此处使用的一些示例。
下面是一个使用CsvDozerBeanReader. 您会注意到它与CsvBeanReader示例几乎相同,但是:
它使用不同的阅读器(废话!)
它使用索引映射,例如 attribs[0]
它通过调用configureBeanMapping()来设置映射(而不是在read()方法上接受一个字符串数组,例如CsvBeanReader
它还设置了一些提示(更多内容见下文)
代码:
package org.supercsv.example;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.dozer.CsvDozerBeanReader;
import org.supercsv.io.dozer.ICsvDozerBeanReader;
import org.supercsv.prefs.CsvPreference;
public class ReadWithCsvDozerBeanReader {
private static final String CSV =
"firstname, lastname, dog_name, fav_hat, fav_color\n"
+ "bill,smith,fido,porkpie,blue\n"
+ "james,smith,rover,bowler,purple";
private static final String CSV2 =
"firstname, lastname, car_type, floor_number\n"
+ "tom, collins, ford, 14\n"
+ "jim, jones, toyota, 120";
// attributes start at element 2 of the header array
private static final int ATT_START_INDEX = 2;
// custom preferences required because CSV contains spaces that aren't part of the data
private static final CsvPreference PREFS = new CsvPreference.Builder(CsvPreference.STANDARD_PREFERENCE)
.surroundingSpacesNeedQuotes(true).build();
public static void main(String[] args) throws IOException {
System.out.println("CsvDozerBeanReader with first CSV input:");
readWithCsvDozerBeanReader(new StringReader(CSV));
System.out.println("CsvDozerBeanReader with second CSV input:");
readWithCsvDozerBeanReader(new StringReader(CSV2));
}
private static void readWithCsvDozerBeanReader(final Reader reader) throws IOException {
ICsvDozerBeanReader beanReader = null;
try {
beanReader = new CsvDozerBeanReader(reader, PREFS);
final String[] header = beanReader.getHeader(true);
// set up the field mapping, processors and hints dynamically
final String[] fieldMapping = new String[header.length];
final CellProcessor[] processors = new CellProcessor[header.length];
final Class<?>[] hintTypes = new Class<?>[header.length];
for( int i = 0; i < header.length; i++ ) {
if( i < ATT_START_INDEX ) {
// normal mappings
fieldMapping[i] = header[i];
processors[i] = new NotNull();
} else {
// attribute mappings
fieldMapping[i] = String.format("attribs[%d]", i - ATT_START_INDEX);
processors[i] = new Optional(new ParsePersonAttribute(header));
hintTypes[i] = PersonAttribute.class;
}
}
beanReader.configureBeanMapping(Person.class, fieldMapping, hintTypes);
Person person;
while( (person = beanReader.read(Person.class, processors)) != null ) {
System.out.println(String.format("lineNo=%s, rowNo=%s, person=%s",
beanReader.getLineNumber(),
beanReader.getRowNumber(), person));
}
}
finally {
if( beanReader != null ) {
beanReader.close();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
CsvDozerBeanReader with first CSV input:
lineNo=2, rowNo=2, person=Person [firstname=bill, lastname=smith, attribs=[PersonAttribute [key=dog_name, value=fido], PersonAttribute [key=fav_hat, value=porkpie], PersonAttribute [key=fav_color, value=blue]]]
lineNo=3, rowNo=3, person=Person [firstname=james, lastname=smith, attribs=[PersonAttribute [key=dog_name, value=rover], PersonAttribute [key=fav_hat, value=bowler], PersonAttribute [key=fav_color, value=purple]]]
CsvDozerBeanReader with second CSV input:
lineNo=2, rowNo=2, person=Person [firstname=tom, lastname=collins, attribs=[PersonAttribute [key=car_type, value=ford], PersonAttribute [key=floor_number, value=14]]]
lineNo=3, rowNo=3, person=Person [firstname=jim, lastname=jones, attribs=[PersonAttribute [key=car_type, value=toyota], PersonAttribute [key=floor_number, value=120]]]
Run Code Online (Sandbox Code Playgroud)
在将这个例子放在一起时,我发现了CsvDozerBeanReaderSuper CSV 2.0.1 中的一个错误,当您将单元处理器(例如我在上面的示例中创建的用于解析每个人的属性键/值的处理器)与索引映射结合使用时,例如:
"firstname","lastname","attribs[0]","attribs[1]"
Run Code Online (Sandbox Code Playgroud)
我刚刚发布了解决此问题的 Super CSV 2.1.0。事实证明,Dozer 需要为索引映射配置一个提示才能正常工作。我不是 100% 确定为什么,因为PersonAttribute当您摆脱自定义单元处理器并使用以下(深度)映射时,它非常有能力创建每个并将其添加到正确的索引中:
"firstname","lastname","attribs[0].value","attribs[1].value"
Run Code Online (Sandbox Code Playgroud)
我希望这有帮助 :)