OpenCSV - Как сопоставить выбранные столбцы с Java Bean независимо от порядка?
У меня есть файл CSV со следующими столбцами: id
, fname
, telephone
, lname
, address
.
У меня есть класс Person
с членами id
, fname
и lname
. Я хочу сопоставить только эти столбцы с объектом Person
из файла CSV и отбросить столбцы telephone
и address
. Как я могу это сделать? Решение должно масштабироваться по мере добавления большего количества столбцов в будущем. И должен работать независимо от положения столбца.
В идеальном решении пользователь будет указывать только столбцы для чтения, и он должен просто работать.
Ответы
Ответ 1
Вы можете использовать HeaderColumnNameTranslateMappingStrategy. Давайте предположим, что для упрощения CSV имеет следующие столбцы: Id
, Fname
, Telephone
, Lname
, Address
.
CsvToBean<Person> csvToBean = new CsvToBean<Person>();
Map<String, String> columnMapping = new HashMap<String, String>();
columnMapping.put("Id", "id");
columnMapping.put("Fname", "fname");
columnMapping.put("Lname", "lname");
HeaderColumnNameTranslateMappingStrategy<Person> strategy = new HeaderColumnNameTranslateMappingStrategy<Person>();
strategy.setType(Person.class);
strategy.setColumnMapping(columnMapping);
List<Person> list = null;
CSVReader reader = new CSVReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("test.csv")));
list = csvToBean.parse(strategy, reader);
Сопоставление столбцов будет отображать столбцы с вашим объектом Person
.
Ответ 2
Последние версии OpenCSV обесценивают метод parse(X, Y)
, и он снова начал использовать BeanBuilder, поэтому верхний ответ устарел.
try {
CsvToBeanBuilder<PersonCSV> beanBuilder = new CsvToBeanBuilder<>(new InputStreamReader(new FileInputStream("your.csv")));
beanBuilder.withType(PersonCSV.class);
// build methods returns a list of Beans
beanBuilder.build().parse().forEach(e -> log.error(e.toString()));
} catch (FileNotFoundException e) {
log.error(e.getMessage(), e);
}
Эти методы позволяют очистить код и удалить MappingStrategy (вы все равно можете использовать его, если хотите спагетти), поэтому вы можете аннотировать свой CSV-класс следующим образом:
@CsvDate("dd/MM/yyyy hh:mm:ss")
@CsvBindByName(column = "Time Born", required = true)
private Date birthDate;
Ответ 3
Я не могу говорить за opencsv, но это легко достижимо с помощью Super CSV, который имеет два разных readers, которые поддерживают частичное чтение (игнорируя столбцы), а также чтение в джавабе. CsvDozerBeanReader
даже способен глубокое и индексированное сопоставление, поэтому вы можете сопоставить вложенные поля.
Мы (команда Super CSV) только что выпустили версию 2.0.0, которая доступна в Maven central или SourceForge.
Обновление
Вот пример (на основе теста в проекте GitHub, который вы создали), который использует Super CSV вместо opencsv. Обратите внимание, что для предпочтений CSV необходим флаг surroundingSpacesNeedQuotes
, поскольку ваш пример CSV файла недопустим (он имеет пробелы между полями - пробелы считаются частью данных в CSV).
ICsvBeanReader beanReader = null;
try {
beanReader = new CsvBeanReader(
new InputStreamReader(
ClassLoader.getSystemResourceAsStream("test.csv")),
new CsvPreference.Builder(CsvPreference.STANDARD_PREFERENCE)
.surroundingSpacesNeedQuotes(true).build());
List<String> columnsToMap = Arrays.asList("fname", "telephone", "id");
// read the CSV header (and set any unwanted columns to null)
String[] header = beanReader.getHeader(true);
for (int i = 0; i < header.length; i++) {
if (!columnsToMap.contains(header[i])) {
header[i] = null;
}
}
Person person;
while ((person = beanReader.read(Person.class, header)) != null) {
System.out.println(person);
}
} finally {
beanReader.close();
}
Ответ 4
Используйте uniVocity-parsers и покончите с этим. Неважно, как организованы столбцы во входном CSV, будут проанализированы только те, которые вам нужны.
При записи столбцы, которые у вас есть в классе, будут записаны в соответствующие столбцы, а остальные будут пустыми.
Здесь класс с некоторыми примерами:
class TestBean {
// if the value parsed in the quantity column is "?" or "-", it will be replaced by null.
@NullString(nulls = { "?", "-" })
// if a value resolves to null, it will be converted to the String "0".
@Parsed(defaultNullRead = "0")
private Integer quantity; // The attribute type defines which conversion will be executed when processing the value.
@Trim
@LowerCase
// the value for the comments attribute is in the column at index 4 (0 is the first column, so this means fifth column in the file)
@Parsed(index = 4)
private String comments;
// you can also explicitly give the name of a column in the file.
@Parsed(field = "amount")
private BigDecimal amount;
@Trim
@LowerCase
// values "no", "n" and "null" will be converted to false; values "yes" and "y" will be converted to true
@BooleanString(falseStrings = { "no", "n", "null" }, trueStrings = { "yes", "y" })
@Parsed
private Boolean pending;
}
Здесь, как получить список TestBean
BeanListProcessor<TestBean> rowProcessor = new BeanListProcessor<TestBean>(TestBean.class);
CsvParserSettings parserSettings = new CsvParserSettings();
parserSettings.setRowProcessor(rowProcessor);
parserSettings.setHeaderExtractionEnabled(true);
CsvParser parser = new CsvParser(parserSettings);
parser.parse(getReader("/examples/bean_test.csv"));
List<TestBean> beans = rowProcessor.getBeans();
Раскрытие информации: Я являюсь автором этой библиотеки. Это с открытым исходным кодом и бесплатно (лицензия Apache V2.0).
Ответ 5
Вот хороший способ использовать OpenCSV для преобразования в POJO в целом:
protected <T> List<T> mapToCSV(String csvContent, Class<T> mapToClass) {
CsvToBean<T> csvToBean = new CsvToBean<T>();
Map<String, String> columnMapping = new HashMap<>();
Arrays.stream(mapToClass.getDeclaredFields()).forEach(field -> {
columnMapping.put(field.getName(), field.getName());
});
HeaderColumnNameTranslateMappingStrategy<T> strategy = new HeaderColumnNameTranslateMappingStrategy<T>();
strategy.setType(mapToClass);
strategy.setColumnMapping(columnMapping);
CSVReader reader = new CSVReader(new StringReader(csvContent));
return csvToBean.parse(strategy, reader);
}
public static class MyPojo {
private String foo, bar;
public void setFoo(String foo) {
this.foo = foo;
}
public void setBar(String bar) {
this.bar = bar;
}
}
Затем из вашего теста вы можете использовать:
List<MyPojo> list = mapToCSV(csvContent, MyPojo.class);
Ответ 6
Я реализовал гибкое решение для решения этой проблемы. Он очень прост в использовании и код с примером доступен на моем github ниже:
https://github.com/jsinghfoss/opencsv
Ответ 7
SimpleFlatMapper может сделать это легко
см. Начало работы csv, используя заголовки csv или указав вручную, какие столбцы соответствуют какому свойству.
CsvParser
.mapTo(MyObject.class)
.forEach(file, System.out::println);
Ответ 8
Посмотрите на jcsvdao, https://github.com/eric-mckinley/jcsvdao/, использует файлы сопоставления стиля hibernate и может обрабатывать отношения 1to1 и 1toMany.
Хорошо, если вы не владеете файлами csv, так как имеют гибкие стратегии соответствия.
Ответ 9
Использование примера jcvsdao
Пример файла CSV пользователя
Username, Email, Registration Date, Age, Premium User
Jimmy, [email protected], 04-05-2016, 15, Yes, M
Bob, [email protected], 15-01-2012, 32, No, M
Alice, [email protected], 22-09-2011, 24, No, F
Mike, [email protected], 11-03-2012, 18, Yes, M
Helen, [email protected], 02-12-2013, 22, Yes, F
Tom, [email protected], 08-11-2015, 45, No, M
Создать CsvDao
CSVDaoFactory factory = new CSVDaoFactory("/csv-config.xml");
CSVDao dao = new CSVDao(factory);
List<UserDetail> users = dao.find(UserDetail.class);
CSV-config.xml
<CSVConfig>
<mappingFiles fileType="resource">
<mappingFile>/example01/mapping/UserDetail.csv.xml</mappingFile>
</mappingFiles>
</CSVConfig>
UserDetail.csv.xml
<CSVMapping className="org.jcsvdao.examples.example01.model.UserDetail" csvFile="csv-examples/example01/users.txt" delimiter="," ignoreFirstLine="true">
<matchAll/>
<properties>
<property index="0" property="username" primaryKey="true"/>
<property index="1" property="email"/>
<property index="2" property="registrationDate" converter="myDateConverter"/>
<property index="3" property="age"/>
<property index="4" property="premiumUser" converter="yesNoConverter"/>
<property index="5" property="gender" converter="myGenderConverter"/>
</properties>
<converters>
<dateConverter converterName="myDateConverter" format="dd-MM-yyyy"/>
<booleanConverter converterName="yesNoConverter" positive="Yes" negative="No"/>
<customConverter converterName="myGenderConverter" converterClass="org.jcsvdao.examples.example01.converter.GenderCustomerConverter"/>
</converters>
</CSVMapping>