Version 4 changes

the following code should still work with version 4. The only difference is the lightning-csv module is lighter and does not contains any mapping related code.

Why?

Are you looking for a very performant Csv Parser, or even better an easy to use CsvMapper?

sfm-csv provides the most flexible CsvMapper available. It supports Constructor injection, Factory method, Builder Pattern… The new java8 time API, the old joda time API. Inner object mapping, join mapping.

Give it a try.

Getting Started Csv

Maven Central JavaDoc

Setting up the environment

Add the Dependency to your build.

for Maven:

Java 8, 9, 10 , 11 no module-info

<dependency>
    <groupId>org.simpleflatmapper</groupId>
    <artifactId>sfm-csv</artifactId>
    <version>8.2.3</version>
</dependency>

Java 6, 7

<dependency>
    <groupId>org.simpleflatmapper</groupId>
    <artifactId>sfm-csv-jre6</artifactId>
    <version>8.2.3</version>
</dependency>

Java 9, 10 , 11 with module-info

<dependency>
    <groupId>org.simpleflatmapper</groupId>
    <artifactId>sfm-csv-jre9</artifactId>
    <version>8.2.3</version>
</dependency>

Reading a csv file

The CsvParser api allows you to read from a File, a Reader or a CharSequence via a CheckedConsumer callback, an Iterator or a Stream of String[]

Source

// Callback
CsvParser
        .forEach(file, row -> System.out.println(Arrays.toString(row)));

// Iterator
try (CloseableIterator<String[]> it = CsvParser.iterator(file)) {
    while(it.hasNext()) {
        System.out.println(Arrays.toString(it.next()));
    }
}

// Stream
try (Stream<String[]> stream = CsvParser.stream(file)) {
    stream.forEach(row -> System.out.println(Arrays.toString(row)));
}

Writing a csv file

Since 8.1.0 the org.simpleflatmapper.lightningcsv.CsvWriter provides a simple api to Writer csv rows and cells to a File or an Appendable.

 try(ClosableCsvWriter writer = CsvWriter.dsl().to(file)) {
   writer
     .appendRow("hello", "world!\nnew line")
     .appendRow("something");
 }

The default encoding will be UTF-8, it is possible to specify and different Charset in the to method.

See unit test for more example.

Mapping a csv to an object

You can also ask the row to be mapped to an object. You can then read the csv from a File, a Reader or a CharSequence via a CheckedConsumer callback, an Iterator or a Stream of your type. The mapper will use the header row - the first one - to match against the property of the object. You can also specify the headers manually if there none or if you want to skip them.

Source

// Callback
CsvParser
        .mapTo(MyObject.class)
        .forEach(file, System.out::println);

// Iterator
try (CloseableIterator<MyObject> it =
             CsvParser.mapTo(MyObject.class).iterator(file)) {
    while(it.hasNext()) {
        System.out.println(it.next());
    }
}

// Stream
CsvParser
        .mapTo(MyObject.class)
        .stream(
            file, 
            (s) -> { s.forEach(System.out::println); return null; }
        )

// override headers
CsvParser
        .skip(1)
        .mapTo(MyObject.class)
        .headers("id", "email", "name")
        .forEach(file, System.out::println);

Customizing the date format

By default it will use parse the date using a "yyyy-MM-dd HH:mm:ss" formatter. That obviously won’t work for everybody and it is possible to override it on a column by column basis.

// overrides the default format
CsvParser
        .mapWith(
                CsvMapperFactory
			.newInstance()
			.defaultDateFormat("yyyy-MM-dd")
			.newMapper(MyClass.class))
        .forEach(file, System.out::println);

// overrides the format for a specific column
CsvParser
        .mapWith(
                CsvMapperFactory
                        .newInstance()
			.addColumnProperty("my_date_col", new DateFormatProperty("yyyy-MM-dd"))
                        .newMapper(MyClass.class))
        .forEach(file, System.out::println);

// overrides the format for any column with date in the name 
CsvParser
        .mapWith(
                CsvMapperFactory
                        .newInstance()
                        .addColumnProperty(k -> k.getName().contains("date"), new DateFormatProperty("yyyy-MM-dd"))
                        .newMapper(MyClass.class))
        .forEach(file, System.out::println);

Writing a csv from an object

The CsvWriter allows you to create append object to an Appendable. If no headers are specified it will generate a list of headers from the properties of the object. Though it is better to specify the headers manually.

// better to cache the dsl with the from 
// to avoid recomputing the object metadata
CsvWriter.CsvWriterDSL<MyObject> writerDsl =
    CsvWriter.from(MyObject.class).columns("id" ,"name", "email");

public void writeCsv(Collection<MyObject> objects, File file) 
                                                throws IOException {
    try (FileWriter fileWriter = new FileWriter(file)) {
        CsvWriter<MyObject> writer=
                writerDsl.to(fileWriter);
        objects.forEach(CheckedConsumer.toConsumer(writer::append));
    }
}

writing with headers not matching the property name

if you want to use a header that does not match the name of the property, for example, if you need the email header to be “contact” you will need to add an alias by adding RenameProperty on the column.

// better to cache the dsl with the from 
// to avoid recomputing the object metadata
CsvWriter.CsvWriterDSL<MyObject> writerDsl =
    CsvWriter
        .from(MyObject.class)
        .columns("id" ,"name")
        .column("contact", new RenameProperty("email"));

public void writeCsv(Collection<MyObject> objects, File file) 
                                                throws IOException {
    try (FileWriter fileWriter = new FileWriter(file)) {
        CsvWriter<MyObject> writer=
                writerDsl.to(fileWriter);
        objects.forEach(CheckedConsumer.toConsumer(writer::append));
    }
}