XML und JSON sind fantastische, leichtgewichtige und technisch gut zu verarbeitende Schnittstellen-Formate. Es gibt da aber ein weiteres Format, welches den Beiden den Rang abläuft, zumindest wenn es um die inflationäre Verwendung geht: CSV. Wenn wir einmal nicht darum herum kommen CSV Dateien zu lesen oder zu schreiben, sollten wir zumindest versuchen dies auf eine Art und Weise zu tun die unsere Nerven schont. Ein solcher Weg: OpenCSV
Die Grundidee hinter OpenCSV ist weder neu noch kompliziert. Ähnlich wie bei JPA, JSON oder XML versehen wir unser Model oder DTO-Klassen mit Annotationen die später für das Lesen / Schreiben der Daten sorgen soll. Später im Programm übergeben wir den entsprechenden Funktionen nur einen InputStream und eine so annotierte Klasse und für jede Zeile innerhalb unserer CSV Datei erhalten wir eine Objekte-Instanz ( bzw. beim Schhreiben: für jede Objektinstanz eine Zeile in der CSV Datei).
Für die einfachste Form dieser Annotationen existieren zwei Varianten:
@CsvBindByPosition(position = 0)
private Long id;
@CsvBindByName(column = "customerid")
private Long customerid;
Hier wird festgelegt ob das Mapping basierend auf der Position (Variante 1) oder basierend auf einer Kopfzeile innerhalb der CSV Datei (Variante 2) geschehen soll. Das Einlesen einer CSV Datei ist dann schnell erledigt:
demo.csv
customerid;firstname;lastname;registerdate;discount
1;Max;Muster;01.05.2010;5
2;Adam;Bean;20.06.2011;10
3;Lisa;Müller;08.08.2012;15
4;Lischen;Müller;27.09.2013;20
einlesen:
List<Customer> beans = new CsvToBeanBuilder<Customer>(...inputstream...)
.withSeparator(';')
.withType(Customer.class)
.build().parse();
Der Vorteil liegt auf der Hand. Dank der Annotationen haben wir eine sehr gut verständliche und wartbare Struktur welche die CSV Datei wiederspiegelt.
Oftmals reicht ein solches “einfaches” Mapping natürlich nicht aus, da wir möglicherweise ganze Objektgraphen in eine flache CSV-Struktur bringen wollen. Zum einen wird beim Schreiben von CSV Dateien der entsprechende Getter aufgerufen wo ein einfaches Mapping statt finden kann. Flexibler und nicht nur für das Schreiben sondern auch für das Lesen von CSV Dateien sind Converter die registriert werden können:
public class Material {
@CsvBindByPosition(position = 0)
private Long id;
@CsvBindByPosition(position = 1)
private String description;
@CsvBindAndSplitByPosition(position = 2, elementType = Price.class, splitOn = "\\|", converter = PriceConverter.class)
private List<Price> prices;
@CsvBindByPosition(position = 3)
private Double averagePrice;
public Double getAveragePrice() {
return prices.stream().map(Price::getPrice)
.mapToDouble(BigDecimal::doubleValue)
.average()
.orElse(0.);
}
}
PriceConverter.java :
public class PriceConverter extends AbstractCsvConverter {
private final DecimalFormat decimalFormat = new DecimalFormat("#,##");
@Override
public String convertToWrite(Object value) throws CsvDataTypeMismatchException {
final Price price = (Price) value;
final String priceValue = decimalFormat.format((price).getPrice());
final String until = price.getValidTo().format(DateTimeFormatter.ISO_DATE);
final String from = price.getValidTo().format(DateTimeFormatter.ISO_DATE);
return String.format("%s*%s*%s|", from, until, priceValue);
}
@Override
public Object convertToRead(String value) throws CsvDataTypeMismatchException, CsvConstraintViolationException {
final String[] splits = value.trim().split("\\*");
LocalDate from=LocalDate.parse(splits[0], DateTimeFormatter.ISO_DATE);
LocalDate to=LocalDate.parse(splits[1], DateTimeFormatter.ISO_DATE);
BigDecimal price = new BigDecimal(splits[2]);
return new Price(from, to, price);
}
}
Für die Vollständigkeit, das Schreiben:
Writer writer = new FileWriter("out.csv");
StatefulBeanToCsv beanToCsv = new StatefulBeanToCsvBuilder(writer).withSeparator(';').build();
beanToCsv.write(materials);
writer.close();
Es gibt noch einige mehr Annotationen ( Datumsformat, individuelles Mapping für Schreiben und Lesen…) mit dessen Hilfe wir in den Prozess eingreifen können. Die Bibliothek ist bezüglich seines Umfangs sehr übersichtlich, bietet aber genug Stellen sehr individuell auf das Schreiben und Lesen von CSV Dateien ein zu wirken. Allemal ein Blick Wert.