OpenCSV - How to map selected columns to Java Bean regardless of order?
I have a CSV file with the following columns: id
, fname
, telephone
, lname
, address
.
I have a Person
class with id
, fname
and lname
data members. I want to map only these columns to Person
object from a CSV file and discard telephone
and address
columns. How can I do this? The solution must scale as more columns are added in future. And should work regardless of column position.
In an ideal solution user will only specify columns to read and it should just work.
You can use HeaderColumnNameTranslateMappingStrategy. Lets assume your CSV has the following columns: Id
, Fname
, Telephone
, Lname
, Address
for the sake of simplicity.
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);
The columnMapping will map the columns with your Person
object.
Recent versions of OpenCSV deprecate the method parse(X, Y)
and it's recommenced to use BeanBuilder instead, so the top answer is out of date.
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);
}
This methods allows you to clean up the code and remove MappingStrategy (you can still use it if you like spaghetti), so you can annotate your CSV class as follows:
@CsvDate("dd/MM/yyyy hh:mm:ss")
@CsvBindByName(column = "Time Born", required = true)
private Date birthDate;
I can't speak for opencsv, but this is easily achievable using Super CSV, which has two different readers that support partial reading (ignoring columns), as well as reading into a Javabean. CsvDozerBeanReader
is even capable of deep and index-based mapping, so you can map to nested fields.
We (the Super CSV team) have just released version 2.0.0, which is available from Maven central or SourceForge.
Update
Here's an example (based on the test in the GitHub project you've created), that uses Super CSV instead of opencsv. Note the CSV preferences needed the surroundingSpacesNeedQuotes
flag enabled as your example CSV file isn't valid (it has spaces between the fields - spaces are considered part of the data in 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();
}