CLOVER🍀

That was when it all began.

Jackson Text Dataformats Moduleを䜿っお、CSVファむルの読み曞きをしおみる

これは、なにをしたくお曞いたもの

これたでCSVを読み曞きするのに、OpenCSV、Super CSVなどを䜿っおきたのですが、JacksonでもCSVファむルの読み曞きが
できるこずはなんずなく知っおいたものの䜿ったこずがありたせんでした。

ちょっず詊しおみようかな、ず思いたす。

どうやら速いみたいですし。

GitHub - uniVocity/csv-parsers-comparison: Comparisons among all Java-based CSV parsers in existence

Jackson Data Format Module

JacksonでCSVファむルを読み曞きするモゞュヌルは、Jackson Data Format Moduleのひず぀になっおいたす。GitHubリポゞトリは、
こちらになりたす。

GitHub - FasterXML/jackson-dataformats-text: Uber-project for (some) standard Jackson textual format backends: csv, properties, yaml (xml to be added in future)

このリポゞトリに含たれおいるモゞュヌルでは、CSV、properties、toml2.12.3から、yamlに察応しおいたす。

ドキュメントは、WikiやCSVモゞュヌル内のREADME.mdを芋るこずになりたす。

Home · FasterXML/jackson-dataformats-text Wiki · GitHub

CsvSchema · FasterXML/jackson-dataformats-text Wiki · GitHub

https://github.com/FasterXML/jackson-dataformats-text/blob/jackson-dataformats-text-2.12.3/csv/README.md

あずは、Javadocですね。

Jackson-dataformat-CSV 2.12.3 API

DatabindずCoreにも䟝存しおいるので、こちらも芋るずよいでしょう。

jackson-databind 2.12.3 API

Jackson-core 2.12.3 API

JacksonでCSVを読み曞きする

Jacksonを䜿っおCSVファむルを読み曞きするずいっおも、基本ず成るのはJSONを扱う時に䜿っおいたJacksonです。

CsvMapperずいうクラスをたずは䜿うこずになるですが、こちらはObjectMapperのサブクラスだったりしたす。

CsvMapper (Jackson-dataformat-CSV 2.12.3 API)

実際のデヌタの読み曞きに䜿うのも、Jackson Databindのクラスだったりしたす。

オブゞェクトPOJOのプロパティの蚭定に、@JsonFormatなどのアノテヌションを䜿ったりするのも同じです。
JSONではないのですが。

このため、必然的にこのあたりの情報も芋おいくこずになるでしょう。

Home · FasterXML/jackson-databind Wiki · GitHub

Home · FasterXML/jackson-core Wiki · GitHub

あずは、特城的なのがCsvSchemaですね。

CsvSchema (Jackson-dataformat-CSV 2.12.3 API)

こちらを䜿っお、CSVのスキヌマ定矩を行いたす。スキヌマ定矩ずは、CSVの読み曞き時の蚭定に関する以䞋のような項目を
指しおいたす。

  • カラム定矩デフォルトは空
  • ヘッダヌの有無デフォルトはヘッダヌなし
  • 囲み文字の定矩デフォルトは"
  • カラムの区切り文字デフォルトは`,'
  • 配列芁玠を区切るための文字デフォルトは:
  • 改行文字デフォルトは\n
    • CSVファむル䜜成の時のみ䜿う項目で、CSVパヌサヌは\r、\r\n、\nのいずれも扱える
  • ゚スケヌプ文字デフォルトなし
    • CSVパヌサヌのみが䜿う項目で、CSVファむルの曞き出し時は囲み文字を二重にしお出力する
  • 最初のレコヌドを無芖するかどうかデフォルトは無芖しない
  • nullを衚す文字列デフォルトは空文字
  • ヘッダヌレコヌドが存圚する堎合、スキヌマ定矩のカラム名がヘッダヌ名ず䞀臎する必芁があるかデフォルトは䞀臎しなくおよい

個人的には、゚スケヌプ文字が読み蟌み時にしか効かないこずに気づかなくお、けっこうハマりたした 。

説明は、このくらいにしおいく぀かパタヌンを詊しおいっおみたしょう。

お題

こんな感じでやっおみたす。

  • CSVファむルの読み蟌み
    • 区切り文字,、囲み文字"、゚スケヌプ文字\のCSVを読み蟌む
      • ヘッダヌ有り無しの2぀のファむルを甚意する
    • 読み蟌み結果は、Stringの配列、Map、自分で定矩したクラス、の3぀で扱う
  • CSVファむルの曞き蟌み
    • 区切り文字,、囲み文字"のCSVを出力する
      • ヘッダヌ有り無しの2぀のパタヌンで出力する
    • 曞き蟌みに䜿甚するオブゞェクトは、Stringの配列、自分で定矩したクラス、の2぀で扱う

読み蟌み時はMapを入れおも扱いやすかったので入れたしたが、曞き蟌み時はくどくなるのでやめたした 。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-72-generic", arch: "amd64", family: "unix"

Maven䟝存関係は、こちら。

    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-csv</artifactId>
            <version>2.12.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.12.3</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.7.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.7.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

たずはjackson-dataformat-csvが必芁です。こちらで、jackson-databindおよびjackson-core、jackson-annotationsも
掚移的に匕き蟌たれたす。

たた、オブゞェクトにマッピングする際にはLocalDateを䜿うこずにしたので、jackson-datatype-jsr310も含めたした。

GitHub - FasterXML/jackson-modules-java8: Set of support modules for Java 8 datatypes (Optionals, date/time) and features (parameter names)

動䜜確認は、テストコヌドを動かしお行うこずにしたした。 アサヌションはしたせんでしたけど。

読み蟌み察象のCSVファむル

読み蟌み察象のCSVファむルは、こんな感じで甚意したした。お題は曞籍です。

ヘッダヌあり。

src/test/resources/books.csv

isbn,title,price,publishDate
978-4621303252,Effective Java 第3版,"4400","2018-10-30"
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
"978-4774189093","Java本栌入門 \"モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで\"",3278,2017-04-18
978-4774166858,改蚂2版 パヌフェクトJava,3520,2014-11-01

ヘッダヌなし。

src/test/resources/books_headerless.csv

978-4621303252,Effective Java 第3版,"4400","2018-10-30"
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
"978-4774189093","Java本栌入門 \"モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで\"",3278,2017-04-18
978-4774166858,改蚂2版 パヌフェクトJava,3520,2014-11-01

囲み文字の有無は、適圓に仕蟌んでいたす。

マッピング先のクラス

CSVファむルを読み蟌んだ内容を、自分で䜜ったオブゞェクトにマッピングする堎合のクラス。

src/test/java/org/littlewings/jackson/csv/Book.java

package org.littlewings.jackson.csv;

import java.time.LocalDate;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder({"isbn", "title", "price", "publishDate"})  // for CsvMapper#schemaFor
public class Book {
    String isbn;
    String title;
    int price;
    @JsonFormat(pattern = "yyyy-MM-dd")
    LocalDate publishDate;

    public static Book create(String isbn, String title, int price, LocalDate publishDate) {
        Book book = new Book();

        book.setIsbn(isbn);
        book.setTitle(title);
        book.setPrice(price);
        book.setPublishDate(publishDate);

        return book;
    }

    // gettersetterは省略

}

ずころどころJacksonのアノテヌションが入っおいたすが、実際の利甚時に玹介したす。

CSVファむルを読み蟌む

たずは、CSVファむルを読み蟌んでみたしょう。

テストコヌドの雛圢。

src/test/java/org/littlewings/jackson/csv/JacksonCsvReadTest.java

package org.littlewings.jackson.csv;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvParser;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.jupiter.api.Test;

public class JacksonCsvReadTest {
    // ここに、テストコヌドを曞く
}

ここに、テストコヌドを埋めおいく方針ずしたしょう。アサヌションしおたせんけど 。

Stringの配列ずしお読み蟌むヘッダヌあり

最初は、CSVファむルの各レコヌドをStringの配列ずしお読み蟌みたす。

ドキュメントだず、このあたりですね。

CsvSchema / Column definitions / "Unmapped" (no columns)

Data-binding without schema

こんな感じになりたした。

    @Test
    public void readCsvWithHeaderAsStringArray() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withoutHeader()
                        .withSkipFirstDataRow(true)  // 1レコヌド目を飛ばす
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')   // default
                        .withEscapeChar('\\');

        mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY);

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<String[]> iterator =
                    mapper
                            .readerFor(String[].class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                System.out.println(Arrays.stream(iterator.next()).collect(Collectors.joining(", ")));
            }
        }
    }

CsvMapperのむンスタンスを䜜り、それからCsvSchemaの蚭定を行いたす。

        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withoutHeader()
                        .withSkipFirstDataRow(true)  // 1レコヌド目を飛ばす
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')   // default
                        .withEscapeChar('\\');

        mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY);

CsvSchema#emptySchemaでデフォルトのスキヌマ定矩が返るので、これをベヌスに必芁に応じお蚭定を倉えおいきたす。

読み蟌むCSVファむルはヘッダヌありなのですが、ヘッダヌの倀を読んでもらっおも困るので、ヘッダヌなし、
最初のレコヌドを読み飛ばす蚭定にしたした。

CsvParser.Feature.WRAP_AS_ARRAYは、レコヌドの構成芁玠を配列ずしお返すので指定しおいたす。その他、Listにする時などに
䟿利なようです。

CsvParser.Feature (Jackson-dataformat-CSV 2.12.3 API)

CSVファむルの読み蟌み、各レコヌドの取埗はこんな感じになりたす。

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<String[]> iterator =
                    mapper
                            .readerFor(String[].class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                System.out.println(Arrays.stream(iterator.next()).collect(Collectors.joining(", ")));
            }
        }

MappingIteratorは、Jackson Databindのクラスですね。

読み蟌み結果のClassクラス、スキヌマ定矩、CSVファむルの読み蟌み元を指定しお、CsvMapperより取埗したす。

            MappingIterator<String[]> iterator =
                    mapper
                            .readerFor(String[].class)
                            .with(schema)
                            .readValues(reader);

こちらを䜿っお、CSVファむルを1レコヌドず぀読んでいくようにしたした。

実行結果。

978-4621303252, Effective Java 第3版, 4400, 2018-10-30
978-4798151120, 独習Java, 3278, 2019-05-15
978-4295008477, 新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], 2860, 2020-03-13
978-4774189093, Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, 2017-04-18
978-4774166858, 改蚂2版 パヌフェクトJava, 3520, 2014-11-01
Stringの配列ずしお読み蟌むヘッダヌなし

ヘッダヌなしのCSVファむルを読み蟌み、Stringの配列ずしお受け取る堎合。

    @Test
    public void readCsvWithoutHeaderAsStringArray() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')   // default
                        .withEscapeChar('\\');

        mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY);

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books_headerless.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<String[]> iterator =
                    mapper
                            .readerFor(String[].class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                System.out.println(Arrays.stream(iterator.next()).collect(Collectors.joining(", ")));
            }
        }
    }

先ほどずの違いは、ヘッダヌなし、最初のレコヌドの読み飛ばしを消しただけですね。

そもそも、デフォルトがヘッダヌなしなので 。

実行結果は、こちら。

978-4621303252, Effective Java 第3版, 4400, 2018-10-30
978-4798151120, 独習Java, 3278, 2019-05-15
978-4295008477, 新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], 2860, 2020-03-13
978-4774189093, Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, 2017-04-18
978-4774166858, 改蚂2版 パヌフェクトJava, 3520, 2014-11-01
Mapずしお読み蟌むヘッダヌあり

次に、各レコヌドをMapずしお読み蟌んでみたしょう。こうするず、ヘッダヌの内容をMapのキヌずしお扱っおくれたす。

こちらの内容ですね。

With column names from first row

    @Test
    public void readCsvWithHeaderAsMap() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withHeader()  // 1レコヌド目をヘッダヌずしお䜿う
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')   // default
                        .withEscapeChar('\\');

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<Map<String, String>> iterator =
                    mapper
                            .readerFor(Map.class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                System.out.println(
                        iterator.next().entrySet().stream().map(e -> e.getKey() + ":" + e.getValue()).collect(Collectors.joining(", "))
                );
            }
        }
    }

なので、ヘッダヌを認識するように蚭定。CsvParser.Feature.WRAP_AS_ARRAYは䞍芁になりたす。

        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withHeader()  // 1レコヌド目をヘッダヌずしお䜿う
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')   // default
                        .withEscapeChar('\\');

CsvMapper#readerForには、MapのClassクラスを指定。

            MappingIterator<Map<String, String>> iterator =
                    mapper
                            .readerFor(Map.class)
                            .with(schema)
                            .readValues(reader);

結果はこちら。

isbn:978-4621303252, title:Effective Java 第3版, price:4400, publishDate:2018-10-30
isbn:978-4798151120, title:独習Java, price:3278, publishDate:2019-05-15
isbn:978-4295008477, title:新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], price:2860, publishDate:2020-03-13
isbn:978-4774189093, title:Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", price:3278, publishDate:2017-04-18
isbn:978-4774166858, title:改蚂2版 パヌフェクトJava, price:3520, publishDate:2014-11-01
Mapずしお読み蟌むヘッダヌなし

今床は、ヘッダヌなしのCSVファむルを読み、Mapずしお受け取りたす。

    @Test
    public void readCsvWithoutHeaderAsMap() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .builder()
                        .addColumns(List.of("isbn", "title", "price", "publishDate"), CsvSchema.ColumnType.STRING)  // カラムを定矩
                        .setColumnSeparator(',')  // default
                        .setQuoteChar('"')  // default
                        .setEscapeChar('\\')
                        .build();

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books_headerless.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<Map<String, String>> iterator =
                    mapper
                            .readerFor(Map.class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                System.out.println(
                        iterator.next().entrySet().stream().map(e -> e.getKey() + ":" + e.getValue()).collect(Collectors.joining(", "))
                );
            }
        }
    }

倉わったポむントずしおは、CsvSchemaの䜜り方ですね。ヘッダヌがないので、キヌの名前をカラムずしお定矩する必芁が
ありたす。

        CsvSchema schema =
                CsvSchema
                        .builder()
                        .addColumns(List.of("isbn", "title", "price", "publishDate"), CsvSchema.ColumnType.STRING)  // カラムを定矩
                        .setColumnSeparator(',')  // default
                        .setQuoteChar('"')  // default
                        .setEscapeChar('\\')
                        .build();

倉わったのは、ここくらいですね。

結果。

isbn:978-4621303252, title:Effective Java 第3版, price:4400, publishDate:2018-10-30
isbn:978-4798151120, title:独習Java, price:3278, publishDate:2019-05-15
isbn:978-4295008477, title:新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], price:2860, publishDate:2020-03-13
isbn:978-4774189093, title:Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", price:3278, publishDate:2017-04-18
isbn:978-4774166858, title:改蚂2版 パヌフェクトJava, price:3520, publishDate:2014-11-01
自分で定矩したクラスで読み蟌むヘッダヌあり

読み蟌み系の最埌は、自分で定矩したクラスで読み蟌むようにしおみたす。

゜ヌスコヌドは、こんな感じに。

    @Test
    public void readCsvWithHeaderAsClass() throws IOException {
        CsvMapper mapper = new CsvMapper();
        mapper.registerModules(new JavaTimeModule());  // for LocalDate / LocalDateTime
        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withHeader()
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')  // default
                        .withEscapeChar('\\');

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<Book> iterator =
                    mapper
                            .readerFor(Book.class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                Book book = iterator.next();

                System.out.printf("isbn: %s, title: %s, price: %d, publishDate: %s%n", book.getIsbn(), book.getTitle(), book.getPrice(), book.getPublishDate());
            }
        }
    }

いきなり脱線したすが、マッピング先のクラスがこんな感じでLocalDateを䜿っおいるので

@JsonPropertyOrder({"isbn", "title", "price", "publishDate"})  // for CsvMapper#schemaFor
public class Book {
    String isbn;
    String title;
    int price;
    @JsonFormat(pattern = "yyyy-MM-dd")
    LocalDate publishDate;

CsvMapperにJavaTimeModuleを登録したす。

        mapper.registerModules(new JavaTimeModule());  // for LocalDate / LocalDateTime

MappingIteratorあたりの䜿い方は倉わりたせんが、CsvSchemaの䜜り方が少し倉わりたす。今回は、CsvMapper#schemaForを
䜿いたす。

        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withHeader()
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')  // default
                        .withEscapeChar('\\');

これで指定したクラスがスキヌマの元になるのですが、察象のクラスには@JsonPropertyOrderアノテヌションで
プロパティの順を指定しおおく必芁がありたす。

@JsonPropertyOrder({"isbn", "title", "price", "publishDate"})  // for CsvMapper#schemaFor
public class Book {

泚意点は、このくらいですね。

結果。

isbn: 978-4621303252, title: Effective Java 第3版, price: 4400, publishDate: 2018-10-30
isbn: 978-4798151120, title: 独習Java, price: 3278, publishDate: 2019-05-15
isbn: 978-4295008477, title: 新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], price: 2860, publishDate: 2020-03-13
isbn: 978-4774189093, title: Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", price: 3278, publishDate: 2017-04-18
isbn: 978-4774166858, title: 改蚂2版 パヌフェクトJava, price: 3520, publishDate: 2014-11-01
自分で定矩したクラスで読み蟌むヘッダヌなし

ヘッダヌなしの堎合は、こちら。

    @Test
    public void readCsvWithoutHeaderAsClass() throws IOException {
        CsvMapper mapper = new CsvMapper();
        mapper.registerModules(new JavaTimeModule());  // for LocalDate / LocalDateTime
        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withoutHeader()
                        .withSkipFirstDataRow(true)  // 1レコヌド目を飛ばす
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')  // default
                        .withEscapeChar('\\');

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<Book> iterator =
                    mapper
                            .readerFor(Book.class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                Book book = iterator.next();

                System.out.printf("isbn: %s, title: %s, price: %d, publishDate: %s%n", book.getIsbn(), book.getTitle(), book.getPrice(), book.getPublishDate());
            }
        }
    }

あらためお説明しなおす内容はないので、割愛。

結果。

isbn: 978-4621303252, title: Effective Java 第3版, price: 4400, publishDate: 2018-10-30
isbn: 978-4798151120, title: 独習Java, price: 3278, publishDate: 2019-05-15
isbn: 978-4295008477, title: 新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], price: 2860, publishDate: 2020-03-13
isbn: 978-4774189093, title: Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", price: 3278, publishDate: 2017-04-18
isbn: 978-4774166858, title: 改蚂2版 パヌフェクトJava, price: 3520, publishDate: 2014-11-01

CSVファむルを曞き蟌む

次は、CSVファむルの曞き蟌みを行っおみたしょう。

テストコヌドの雛圢はこちら。

src/test/java/org/littlewings/jackson/csv/JacksonCsvWriteTest.java

package org.littlewings.jackson.csv;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.List;

import com.fasterxml.jackson.databind.SequenceWriter;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.jupiter.api.Test;

public class JacksonCsvWriteTest {
    String[][] booksArray = {
            {"978-4621303252", "Effective Java 第3版", "4400", "2018-10-30"},
            {"978-4798151120", "独習Java", "3278", "2019-05-15"},
            {"978-4295008477", "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]", "2860", "2020-03-13"},
            {"978-4774189093", "Java本栌入門 \"モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで\"", "3278", "2017-04-18"},
            {"978-4774166858", "改蚂2版 パヌフェクトJava", "3520", "2014-11-01"}
    };

    List<Book> books =
            List.of(
                    Book.create("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30)),
                    Book.create("978-4798151120", "独習Java", 3278, LocalDate.of(2019, 5, 15)),
                    Book.create("978-4295008477", "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]", 2860, LocalDate.of(2020, 3, 13)),
                    Book.create("978-4774189093", "Java本栌入門 \"モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで\"", 3278, LocalDate.of(2017, 4, 18)),
                    Book.create("978-4774166858", "改蚂2版 パヌフェクトJava", 3520, LocalDate.of(2014, 11, 1))
            );

    // ここに、テストを曞く
}

CSVファむルに曞き蟌む芁玠は、事前にフィヌルドに定矩しおおくこずにしたした。

では、各皮コヌドを埋めおいきたす。

Stringの配列を曞き蟌むヘッダヌあり

たずは、Stringの配列ずしお曞き蟌むパタヌンから。ヘッダヌありです。

゜ヌスコヌドは、こんな感じになりたした。

    @Test
    public void writeCsvWithHeaderAsStringArray() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .builder()
                        .addColumns(List.of("isbn", "title", "price", "publishDate"), CsvSchema.ColumnType.STRING)  // カラムを定矩
                        .setUseHeader(true)  // print header
                        .setColumnSeparator(',')  // default
                        .setQuoteChar('"')  // default
                        .build();

        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("target/books_from_array.csv"), StandardCharsets.UTF_8)) {
            SequenceWriter sequenceWriter =
                    mapper
                            .writerFor(String[].class)
                            .with(schema)
                            .writeValues(writer);

            for (String[] values : booksArray) {
                sequenceWriter.write(values);
            }
        }
    }

CsvSchemaの䜜り方は読み蟌み時ずそう倉わりたせんが、ヘッダヌを䜜る分だけカラム定矩が必芁になりたす。

        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .builder()
                        .addColumns(List.of("isbn", "title", "price", "publishDate"), CsvSchema.ColumnType.STRING)  // カラムを定矩
                        .setUseHeader(true)  // print header
                        .setColumnSeparator(',')  // default
                        .setQuoteChar('"')  // default
                        .build();

たた、CsvParser.Feature.WRAP_AS_ARRAYは䞍芁です。

曞き蟌みには、SequenceWriterを䜿い、1レコヌドず぀曞き蟌んでいきたす。

        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("target/books_from_array.csv"), StandardCharsets.UTF_8)) {
            SequenceWriter sequenceWriter =
                    mapper
                            .writerFor(String[].class)
                            .with(schema)
                            .writeValues(writer);

            for (String[] values : booksArray) {
                sequenceWriter.write(values);
            }
        }

結果はこちら。

target/books_from_array.csv

isbn,title,price,publishDate
978-4621303252,"Effective Java 第3版",4400,2018-10-30
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
978-4774189093,"Java本栌入門 ""モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで""",3278,2017-04-18
978-4774166858,"改蚂2版 パヌフェクトJava",3520,2014-11-01

ヘッダヌも出力されおいたす。

囲み文字の゚スケヌプは、囲み文字を2぀重ねるこずで行いたす。読み蟌み時ず違い、こちらは蚭定ができたせん。

Stringの配列を曞き蟌むヘッダヌなし

ヘッダヌなしの堎合。

    @Test
    public void writeCsvWithoutHeaderAsStringArray() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"');  // default

        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("target/books_from_array_headerless.csv"), StandardCharsets.UTF_8)) {
            SequenceWriter sequenceWriter =
                    mapper
                            .writerFor(String[].class)
                            .with(schema)
                            .writeValues(writer);

            for (String[] values : booksArray) {
                sequenceWriter.write(values);
            }
        }
    }

ヘッダヌがないので、すごくシンプルになりたす。

結果は、こちら。

target/books_from_array_headerless.csv

978-4621303252,"Effective Java 第3版",4400,2018-10-30
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
978-4774189093,"Java本栌入門 ""モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで""",3278,2017-04-18
978-4774166858,"改蚂2版 パヌフェクトJava",3520,2014-11-01
自分で定矩したクラスで曞き蟌むヘッダヌあり

最埌は、自分で定矩したクラスで曞き蟌む堎合です。たずは、ヘッダヌありの堎合から。

    @Test
    public void writeCsvWithHeaderAsClass() throws IOException {
        CsvMapper mapper = new CsvMapper();
        mapper.registerModules(new JavaTimeModule());  // for LocalDate / LocalDateTime
        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withHeader() // print header
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"');  // default

        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("target/books.csv"), StandardCharsets.UTF_8)) {
            SequenceWriter sequenceWriter =
                    mapper
                            .writerFor(Book.class)
                            .with(schema)
                            .writeValues(writer);

            for (Book book : books) {
                sequenceWriter.write(book);
            }
        }
    }

ずいっおも、ここたで来るず本圓に目新しい芁玠はないですね 。

読み蟌み時ず同じく、CsvMapper#schemaForで察象のClassクラスを指定したす。

        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withHeader() // print header
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"');  // default

結果。ヘッダヌありです。

target/books.csv

isbn,title,price,publishDate
978-4621303252,"Effective Java 第3版",4400,2018-10-30
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
978-4774189093,"Java本栌入門 ""モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで""",3278,2017-04-18
978-4774166858,"改蚂2版 パヌフェクトJava",3520,2014-11-01
自分で定矩したクラスで曞き蟌むヘッダヌなし

ヘッダヌなしの堎合。さらっず。

    @Test
    public void writeCsvWithoutHeaderAsClass() throws IOException {
        CsvMapper mapper = new CsvMapper();
        mapper.registerModules(new JavaTimeModule());  // for LocalDate / LocalDateTime
        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"');  // default

        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("target/books_headerless.csv"), StandardCharsets.UTF_8)) {
            SequenceWriter sequenceWriter =
                    mapper
                            .writerFor(Book.class)
                            .with(schema)
                            .writeValues(writer);

            for (Book book : books) {
                sequenceWriter.write(book);
            }
        }
    }

結果。

target/books_headerless.csv

978-4621303252,"Effective Java 第3版",4400,2018-10-30
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
978-4774189093,"Java本栌入門 ""モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで""",3278,2017-04-18
978-4774166858,"改蚂2版 パヌフェクトJava",3520,2014-11-01

たずめ

Jackson Text Dataformats Moduleを䜿っお、CSVファむルを読み曞きしおみたした。

Jacksonがベヌスになっおいるだけあっお、他のCSV関係のラむブラリずちょっず倉わっおいる感じがしたすが、高速なようですし
Jackson自䜓もよく䜿うず思うので、芚えおおいおもよいでしょう。