JavaでオブジェクトをCSV形式かつ複数行ヘッダー＆フィルターして返したい

# 1.はじめに

csv形式で複数行ヘッダーかつフィールド名で出力フィールドをフィルターできるものを作る必要があったため、使用したライブラリと考えたプログラムを記載する。

# 2.CsvMapper/CsvSchema

Jacksonのライブラリ（jackson-dataformat-csv）でオブジェクト(json)をcsvに簡単に変換できる。
今回はこれを使用した。


```
// getter/setterは必須だが省略
class TestObj {
  private String field1 = "aaa";
  private String field2 = null;
  private String field3 = "ccc";
}
```

```java
TestObj obj = new TestObj();
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(TestObj.class).withHeader();

try {
  System.out.println(mapper.writer(schema).writeValueAsString(obj));
} catch(JsonProcessingException e) {
  // エラー処理（仮）
  e.printStackTrace();
}
```
[出力]

```
field1,field2,field3
aaa,,ccc
```

他にも以下のようなことができる

* nullのフィールドを指定文字列で補完

```java
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(TestObj.class).withHeader().withNullValue("<empty>");
```
[出力]

```
field1,field2,field3
aaa,<empty>,ccc
```

* 行の境界とする文字列を指定

```
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(TestObj.class).withHeader().withLineSeparator("|");
```

[出力]

```
"field1","field2","field3"|"aaa",,"ccc"|
```

* Listで複数行出力

```
List<TestObj> objs = new ArrayList<TestObj>() {
  {
    add(new TestObj());
    add(new TestObj());
    add(new TestObj());
  }
};
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(TestObj.class).withHeader();
```

[出力]

```
field1,field2,field3
aaa,,ccc
aaa,,ccc
aaa,,ccc
```

注意が必要なのは、以下のようなネストのオブジェクトの変更には対応していない点である。

```
// csvへの変換可
class TestObj {
  private String field1 = "aaa";
  private String field2 = "bbb";
  private String field3 = "ccc";
}

// csvへの変換不可
class TestObj2 {
  TestObj obj = new TestObj();
  private String field4 = "aaa";
}
```

# 3. 複数行ヘッダー＆フィールドでフィルターして出力

オブジェクトのフィールド名と型名をヘッダー（２行）として出力するものを作成する。

残念ながら、jacksonでは複数ヘッダーやフィルターはサポートされていないため、自前で作成した。

※調べが足りないだけかもしれないが、これができるライブラリを見つけられなかった

```java

public static void main(String[] args) {
	TestObj obj = new TestObj();
	List<Field> fields = Arrays.asList(obj.getClass().getDeclaredFields());
	CsvMapper mapper = new CsvMapper();
	CsvSchema schema = mapper.schemaFor(TestObj.class);
	
	// フィルターするフィールド（本来は外部入力）
	List<String> filterParam = new ArrayList<String>() {
		{
			add("field1");
			add("field3");
		}
	};
	// 複数ヘッダー作成＆フィルター
	String header = createHeader(obj, filterParam, fields);
	List<String> bodies = new ArrayList<>();
	try {
		bodies = Arrays.asList(mapper.writer(schema).writeValueAsString(obj).split(","));
	} catch (JsonProcessingException e) {
		// エラー処理（仮）
		e.printStackTrace();
	}
	String body = filter(bodies, filterParam, fields);
	
	// 本来は繋げて返す
	System.out.println(header);
	System.out.println(body);
}

private static String createHeader(TestObj obj, List<String> param, List<Field> fields) {
	String fieldHeader = fields.stream().filter(o -> param.contains(o.getName())).map(o -> o.getName
)
			.collect(Collectors.joining(","));
	String typeHeader = fields.stream().filter(o -> param.contains(o.getName())).map(o -> {
		String typeName = o.getType().getName();
		return typeName.substring(typeName.lastIndexOf(".") + 1, typeName.length());
	}).collect(Collectors.joining(","));
	return fieldHeader + "\n" + typeHeader;
}

private static String filter(List<String> bodies, List<String> param, List<Field> fields) {
	if (param == null || param.isEmpty()) {
		return bodies.stream().collect(Collectors.joining(","));
	}
	List<String> res = new ArrayList<>();
	for (int i = 0; i < fields.size(); i++) {
		String fieldName = fields.get(i).getName();
		if (param.contains(fieldName)) {
			res.add(bodies.get(i));
		}
	}
	return res.stream().collect(Collectors.joining(","));
}

```

[出力結果]

```
field1,field3
String,String
aaa,ccc
```

# 参考サイト

CsvSchema
http://fasterxml.github.io/jackson-dataformat-csv/javadoc/2.8/com/fasterxml/jackson/dataformat/csv/CsvSchema.html#withoutColumns()

Javaでカンマ区切りで文字列連結するいくつかの方法
https://qiita.com/shisama/items/b27d16b3aeb1baf055b1

Java8での文字列連結
https://qiita.com/lonerydeveloper/items/9f7c977c039ad4d24d30