この記事は Howtelevision Advent Calendar 2022 18日目の記事です。
17日目は mayuko_okamoto さんによる「数字アレルギーのド文系が元エンジニアに教えを乞うてRedash使ってみた」 でした。
今月12月にハウテレビジョンに入社しました川原です。主に外資就活ドットコムの開発をしています。
弊社が運営する外資就活ドットコムではバックエンドにGoを採用しています。そこで個人的な練習としてGoを用いてCSVファイルの処理を実装してみました。
やったこと
あらかじめダウンロードしておいたクレジットカード明細のファイルを用意し、プロジェクト直下のディレクトリでgo run
するとクレジットカードの明細からほしい情報を読み込んでDBに保存するものです。
% go run main.go
{"ID":"","Date":"2022/11/04","StoreName":"iD/ご利用分","Amount":"80","Month":"12"}
{"ID":"","Date":"2022/11/04","StoreName":"iD/ご利用分","Amount":"160","Month":"12"}
{"ID":"","Date":"2022/11/04","StoreName":"AMAZON.CO.JP","Amount":"3080","Month":"12"}
{"ID":"","Date":"2022/11/08","StoreName":"AMAZON.CO.JP","Amount":"784","Month":"12"}
{"ID":"","Date":"2022/11/10","StoreName":"iD/ご利用分","Amount":"113","Month":"12"}
...
...
処理について
行ったことは次の3点です。
- CSV ファイルを読み込む
- 読み込んだ CSV ファイルのデータから変数に値を渡す
- 値を DB に書き込む
保存したい情報の構造体を定義しておきます。
今回は
- 支払い日時
- 店名
- 金額
- 支払い月
を明細のためのPaymentDetailオブジェクトのプロパティに持たせています。
type PaymentDetail struct {
ID string
Date string
StoreName string
Amount string
Month string
}
明細の CSV ファイルをプロジェクト内の ./data
ディレクトリ配下に配置しておき、ここからデータを取得することにします。任意の path が指定できれば何でも良さそうです。
files, _ := ioutil.ReadDir("./data")
var filePathList []string
for _, file := range files {
if filepath.Ext(file.Name()) == ".csv" {
filePathList = append(filePathList, "./data/"+file.Name())
}
}
下記のコードで CSV から一行ずつ取得した明細データは["2022/7/11", "翌月一括", "ドコモご利用料金", "8692"]
のような形式だったので、 key
が何番目かで判定してオブジェクトに詰めていくことにしました。
CSV ファイルから取得した2行目の配列の2番目の値が明細の月がわかるものだったのでそこで月の値を取得しています。
1行ごとに一つのオブジェクトに渡して insert しています。(結果的にやったことはこれだけなのでオブジェクトを経由する必要がなかったかもしれないです)
for _, v := range filePathList {
f, err := os.Open(v)
if err != nil {
panic(err.Error())
}
r := csv.NewReader(transform.NewReader(f, japanese.ShiftJIS.NewDecoder()))
r.LazyQuotes = true
r.FieldsPerRecord = -1
var month string
for i := 0; ; i++ {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
panic(err.Error())
}
if i == 1 {
month = record[2][4:6]
}
// 最初の 3 レコードは明細ではないので
if i < 3 {
continue
}
// 0: 利用日, 1: 支払い方法, 2: 店名, 3: 金額
paymentDetail := PaymentDetail{Date: record[0], StoreName: record[2], Amount: record[3], Month: month}
if _, err := db.Exec(
`insert payment_details (date, store_name, amount, month) values (?, ?, ?, ?)`,
paymentDetail.Date, paymentDetail.StoreName, paymentDetail.Amount, paymentDetail.Month,
); err != nil {
panic(err.Error())
}
defer db.Close()
paymentDetails = append(paymentDetails, paymentDetail)
if err := json.NewEncoder(os.Stdout).Encode(paymentDetail); err != nil {
panic(err.Error())
}
}
}
処理は全てmain関数内に収めてしまってますがだいたい100行くらいでした。 Go は行数が多くなるイメージでしたが簡単な処理なら Go のビルトインライブラリのみでもこれくらいに収まりました。
ちなみに現状のツールだと手動でファイルを持ってこないといけない実用面では MoneyForward とか使った方が明らかに良さそうですね。(PC 上でのみ明細の管理が完結するならありかもしれない…?)
最後に
ここまでありがとうございました。この記事へのご感想や誤りのご指摘などありましたらお気軽にお寄せください。
また、ハウテレビジョンでは一緒に働いてくれるエンジニアを募集しています。
参考
https://golang.hateblo.jp/entry/golang-encoding-csv
https://zenn.dev/syo_yamamoto/articles/1fb502ef862490