GEDOPLAN
Jakarta EE (Java EE)Java SE

OpenCSV

Jakarta EE (Java EE)Java SE
csv smile

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

csv smile

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.

Github? Klar.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Bitte füllen Sie dieses Feld aus.
Bitte füllen Sie dieses Feld aus.
Bitte gib eine gültige E-Mail-Adresse ein.
Sie müssen den Bedingungen zustimmen, um fortzufahren.

Autor

Diesen Artikel teilen

LinkedIn
Xing

Gibt es noch Fragen?

Fragen beantworten wir sehr gerne! Schreibe uns einfach per Kontaktformular.

Kurse

weitere Blogbeiträge

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!