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";
}
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のフィールドを指定文字列で補完
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. 複数行ヘッダー&フィールドでフィルターして出力
オブジェクトのフィールド名と型名をヘッダー(2行)として出力するものを作成する。
残念ながら、jacksonでは複数ヘッダーやフィルターはサポートされていないため、自前で作成した。
※調べが足りないだけかもしれないが、これができるライブラリを見つけられなかった
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
参考サイト
Javaでカンマ区切りで文字列連結するいくつかの方法
https://qiita.com/shisama/items/b27d16b3aeb1baf055b1
Java8での文字列連結
https://qiita.com/lonerydeveloper/items/9f7c977c039ad4d24d30