はじめに
GoでCSVの生成をしたいときに縛りが緩い汎用的な処理が欲しかったので、構造体のスライスを突っ込めば良いだけの処理を作成しました。
#成果物
file.go
package file
import (
"encoding/csv"
"fmt"
"os"
"reflect"
"strconv"
)
func GenerateCSV(i interface{}, fileName string) {
reflectType := reflect.TypeOf(i)
reflectTypeElem := reflectType.Elem()
if reflectType.Kind() != reflect.Slice || reflectTypeElem.Kind() != reflect.Struct {
fmt.Println("構造体のスライスのみ使えます")
return
}
name := fmt.Sprintf("/tmp/%s.csv", fileName)
f, err := os.Create(name)
if err != nil {
panic(err)
}
defer f.Close()
writer := csv.NewWriter(f)
writer.Write(buildHeader(reflectTypeElem))
reflectValue := reflect.Indirect(reflect.ValueOf(i))
for index := 0; index < reflectValue.Len(); index++ {
record := reflectValue.Index(index)
writer.Write(buildBody(record))
}
writer.Flush()
fmt.Printf("CSVが生成されました。 %s\n", name)
}
func buildHeader(rt reflect.Type) []string {
header := []string{}
for i := 0; i < rt.NumField(); i++ {
rs := rt.Field(i)
header = append(header, rs.Tag.Get("json"))
}
return header
}
func buildBody(rv reflect.Value) []string {
body := []string{}
for i := 0; i < rv.Type().NumField(); i++ {
v := rv.Field(i)
switch v.Kind() {
case reflect.Int:
body = append(body, strconv.FormatInt(v.Int(), 10))
case reflect.String:
body = append(body, v.String())
default:
body = append(body, "") // 例外でもOK。お好みで。
}
}
return body
}
#使用例
main.go
type Hoge struct {
ID int `json:"id"`
Name string `json:"name"`
}
type HogeList []Hoge
func test() {
hoges := HogeList{
Hoge{
ID: 1,
Name: "name1",
},
Hoge{
ID: 2,
Name: "name2",
},
}
fileName := "hoge"
file.GenerateCSV(hoges, fileName)
}
#実行結果
hoge.csv
id,name
1,name1
2,name2
#感想
理想はGenerateCSVの引数型をinterfaceではなく縛ることで確認処理を辞めたかったのですが、気軽に使えることを優先してこのような形に落ち着きました。
reflect周りがもう少しスッキリできるかも?と思いつつ今回は一旦ここまでにしようかと思います。