複数のShift JISファイルを１つのJSON(UTF-8)形式でまとめてみた

##経緯
あるお客様の打合せで、あるSIerに「店舗コードがファイル名になって中身が売上金額のみのファイルがあります。そのファイルが大量にあるのですが、手動で１つのJSONファイルにまとめています。何とか自動化できませんか？」という相談をしていました。
（正確には違う内容ですが、大量のファイルを１ファイルに変換というのは同じです）

SIer の返答は、**「対応するのに、<font color="red">少なくても0.5人月</font>の工数が必要」**とのことでした。

今回も、そんなに難しいのかと思い、GO言語で挑戦してみました。


##ソース

```go:main.go
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"

	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"
	"gopkg.in/ini.v1"
)

type ConfigList struct {
	Path string
}

var Config ConfigList

type JsonForm struct {
	StoreCode string
	Sales     string
}

func init() {
	cfg, _ := ini.Load("./config.ini")
	Config = ConfigList{
		Path: cfg.Section("file").Key("path").String(),
	}
}

func main() {
	CreateJSON(Config.Path)
}

func CreateJSON(ls string) {

	// 指定されたディレクトリ配下のファイルの読み込み
	files, err := ioutil.ReadDir(ls)
	if err != nil {
		log.Fatal(err)
	}
	// ファイル数のチェック
	count := len(files)

	// 現在のディレクトリ
	dr, _ := os.Getwd()
	//出力ファイル名
	//DATE_TIME_FORMAT := "2006/01/02/15:04:05"
	DATE_TIME_FORMAT := "20060102150405"
	now := time.Now().Format(DATE_TIME_FORMAT)

	output := dr + "\\sample" + now + ".txt"

	//ディレクトリの移動
	os.Chdir(ls)

	//ファイルの作成
	file, _ := os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_APPEND, 06666)
	if err != nil {
		//エラー処理
		log.Fatal(err)
	}
	defer file.Close()

	fmt.Fprintln(file, "[") //書き込み

	for i, file := range files {
		fmt.Println(i)
		// ファイルの読み込み
		fmt.Println(file.Name())
		content, err := ioutil.ReadFile(file.Name())
		if err != nil {
			log.Fatal(err)
		}

		// ShiftJISをUTF8に変換(元々UTF8ならこの処理は不要)
		ret, err := ioutil.ReadAll(transform.NewReader(strings.NewReader(string(content)), japanese.ShiftJIS.NewDecoder()))
		if err != nil {
			log.Fatal(err)
		}
		// 改行コードの削除
		s := strings.TrimRight(string(ret), "\r\n")

		//Json形式に変換
		p := JsonForm{file.Name(), s}
		// json に変換して送信したい場合は、Marshalを利用する
		// Marshal を利用する場合には、structでjsonの定義を行う
		v, _ := json.Marshal(p)

		// ファイルの書き込み
		// os.O_RDWR:読み書き、 os.O_CREATE:書き込み、 OS.O_APPEND:追記
		file, _ := os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_APPEND, 06666)
		if err != nil {
			//エラー処理
			log.Fatal(err)
		}
		defer file.Close()

		fmt.Fprint(file, string(v)) //書き込み
		if count-1 > i {
			fmt.Fprintln(file, " ,")
		}

	}

	// jsonファイルの閉じ括弧をつける
	file, _ = os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_APPEND, 06666)
	if err != nil {
		//エラー処理
		log.Fatal(err)
	}
	defer file.Close()

	fmt.Fprintln(file, "]") //書き込み

}
```

```ini:config.ini
[file]
path = C:\fileMerge\sampledata
```


###説明

```go:
import "gopkg.in/ini.v1"

var Config ConfigList

func init() {
	cfg, _ := ini.Load("./config.ini")
	Config = ConfigList{
		Path: cfg.Section("file").Key("path").String(),
	}
}
```

まず、configファイル(iniファイル)を利用するために、`gopkg.in/ini.v1`をインポートします。
後は、上記にあるように `Section`、`Key`を指定すれば設定を読み込めます。
この設定の場所に大量のファイルがある前提にしています。


```go:
func CreateJSON(ls string) {

	// 指定されたディレクトリ配下のファイルの読み込み
	files, err := ioutil.ReadDir(ls)
	if err != nil {
		log.Fatal(err)
	}
	// ファイル数のチェック
	count := len(files)

	// 現在のディレクトリ
	dr, _ := os.Getwd()
	//出力ファイル名
	//DATE_TIME_FORMAT := "2006/01/02/15:04:05"
	DATE_TIME_FORMAT := "20060102150405"
	now := time.Now().Format(DATE_TIME_FORMAT)

	output := dr + "\\sample" + now + ".txt"

}
```
次に`CreateJSON`関数を作成します。
引数は、先ほど取得したconfig.iniのPathです。

`ioutil.ReadDir()`を用いると指定されたフォルダ配下のファイル（フォルダ）一覧が取得できます。今回は、指定フォルダ配下にはファイルのみが存在する前提です。

`count := len(files)`で、取得したファイル（スライス形式）の数を取得しています。
後でforループを回して処理をするのですが、JSON形式のカンマ[,]を付与するタイミングを計るために利用しています。

`dr, _ := os.Getwd()`で現在のディレクトリを取得しているのは、JSON形式で出力するファイルの場所をカレントディレクトにするためです。

`DATE_TIME_FORMAT := "20060102150405"` は、出力ファイル名のファフィックスとして利用するためのフォーマット指定です。
これが面白くて、<font color="red">2006年1月2日の15時4分5秒</font>で、年月日を認識しているようです。


```go:
	//ディレクトリの移動
	os.Chdir(ls)

	//ファイルの作成
	file, _ := os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_APPEND, 06666)
	if err != nil {
		//エラー処理
		log.Fatal(err)
	}
	defer file.Close()

	fmt.Fprintln(file, "[") //書き込み

```

`os.Chdir()`で、処理対象のファイルがあるディレクトリに移動しています。

`file, _ := os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_APPEND, 06666)`で、出力ファイルを作成しています。
詳細は、【[Goでファイル追記・存在しなかったらファイル作成したい！](https://qiita.com/kamol/items/fae07e8533b36f553714)】のサイトをご覧ください。

```go:
for i, file := range files {
		fmt.Println(i)
		// ファイルの読み込み
		fmt.Println(file.Name())
		content, err := ioutil.ReadFile(file.Name())
		if err != nil {
			log.Fatal(err)
		}

		// ShiftJISをUTF8に変換(元々UTF8ならこの処理は不要)
		ret, err := ioutil.ReadAll(transform.NewReader(strings.NewReader(string(content)), japanese.ShiftJIS.NewDecoder()))
		if err != nil {
			log.Fatal(err)
		}
		// 改行コードの削除
		s := strings.TrimRight(string(ret), "\r\n")


}
```

for 文で、先の読み取ったフォルダ配下のファイルをループ処理しています。

`content, err := ioutil.ReadFile(file.Name())`でファイル内容を読みこんでいますが、いろいろやり方があるようです。
[[Golang] ファイル読み込みサンプル](https://qiita.com/tchnkmr/items/b686adc4a7e144d48755)などを参照してください。

`ret, err := ioutil.ReadAll(transform.NewReader(strings.NewReader(string(content)), japanese.ShiftJIS.NewDecoder()))`で読み取った内容をUTF8に変換しています。
UTF8のファイルを読込む場合には、この処理は不要です。

`s := strings.TrimRight(string(ret), "\r\n")`についても、今回は、１テキスト１行という規約の元作成していますが、念のため改行コードを指定しています。
他の改行コードの可能性もありますが、考慮していません。


```go:
p := JsonForm{file.Name(), s}
		// json に変換して送信したい場合は、Marshalを利用する
		// Marshal を利用する場合には、structでjsonの定義を行う
		v, _ := json.Marshal(p)
```

ここで、読みこんだ内容をJSON形式に変換しています。
サイトを忘れてしまいましたが、説明通りコーディングしたら、変換できました。





#結論
対応するのに、**多くても0.5人月**もいらないと思う。


#参考

[GOで文字コードを変換する](http://tanukito.tk/article/462192324.html)
[Go言語での文字列やファイル操作メモ](https://la-zs-site.com/2019/04/01/post-120/)
[Goでファイル追記・存在しなかったらファイル作成したい！](https://qiita.com/kamol/items/fae07e8533b36f553714)
[golangの日付フォーマット指定が面白い](https://twinbird-htn.hatenablog.com/entry/2016/04/26/180747)
[[Golang] ファイル読み込みサンプル](https://qiita.com/tchnkmr/items/b686adc4a7e144d48755)