【Golang】JSON などの外部ファイルをバイナリに埋め込むサンプル【ソースにデータを書かない】【Go 1.16+】

> **Golang で外部ファイルをコンパイル/ビルドしたバイナリに埋め込みたい**。
>
> 例えば JSON ファイルとか、画像とか。多言語の `bind` みたいな感じで。`io/ioutil` も微妙に使い勝手が悪いし、[今後使えなくなる（`deprecated` になった）](https://tip.golang.org/doc/go1.16#ioutil)らしいし。知らんけど。

かと言って、<code>myData := []byte(\`{"hoge": "fuga"}\`)</code> のように**ソースコードに埋め込みたくない**のです。改行とかメンテが面倒なのです。画像なんかも Base64 エンコードとかでハードコーディングしたくないのです。

[「golang ファイル 埋め込み サンプル」で Qiita 記事をググって](https://www.google.com/search?q=site:qiita.com+golang+%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB+%E5%9F%8B%E3%82%81%E8%BE%BC%E3%81%BF+%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB)も、ドンピシャのサンプルがなかったので、自分のググラビリティとして。

## TL; DR <sub><sup><sub><sup>（今北産業）</sup></sub></sup></sub>

1. Golang 1.16 以上で [<ruby>`embed`<rt>埋め込む</rt></ruby> と言う、まんまのパッケージ](https://golang.org/pkg/embed/#pkg-index)が使えるようになった。
2. 埋め込みたいデータの相対パスを、変数定義の直前にコメントで指定する。<br>（`//go:embed <データのパス>`）
3. 複数ファイルを一気に読み込む時は `embed.FS` 型に入れて、`hoge.ReadFile("<データのパス>")` メソッドで取り出す。

## TS; DR <sub><sup><sub><sup>（マスター、とりあえず動くものをくれ）</sup></sub></sup></sub>

### 1 つのファイルを埋め込むサンプル

```golang:./main.go
package main

import (
	_ "embed" // 下記 //go:embed を機能させるために _ で
              // 読み込みだけさせておく。ここ重要。
	"encoding/json"
	"fmt"
)

// データの埋め込み（詳しくは「同梱ファイルのパスについて」参照）
//go:embed data/sample.json
var myRawData []byte

// JSON データの構造を定義
type TData struct {
	Name string `json:"name"`
}

func main() {
	// 埋め込まれたデータから JSON としてパース（Unmarshal）する
	var d TData
	json.Unmarshal(myRawData, &d)

	// パースされたデータの確認
	fmt.Printf("%v\n", d.Name)
}
// Output: hoge
```

```json:data/sample.json
{
    "name": "hoge"
}
```

- [オンラインで動作をみる](https://go.dev/play/p/wT3h67Q_oiA) @ Go Playground
- 動作確認バージョン: Go version 1.16 and 1.18.1

:::note info
【同梱ファイルのパスについて】

```go
//go:embed data/sample.json
var myRawData []byte
```

上記はパス先（`data/sample.json`）のデータを、変数 "`myRawData`" に読み込み、バイナリに埋め込みます。埋め込まれたデータは raw（生）データの `[]byte` 型のままです。

また、この時、対象となるファイルは、このソース（`main.go`）から見たディレクトリ以下に設置されている必要があります。
"`../../hoge/fuga.png`" のように、[上位のディレクトリの参照は仕様で参照できません](https://github.com/golang/go/issues/46056)。
:::

:::note info
**`go:embed only allowed in Go files that import "embed"` エラー**

上記エラーが出る場合、`import` で [embed](https://pkg.go.dev/embed) パッケージが読み込まれていません。
ソース内で明示的に `embed.FS` などのモジュールの関数を使っていない場合、`gofmt` などで消されてしまいます。

単体ファイルを単純に埋め込みたい場合は `import _ "embed"` と強制インポートする必要があります。
:::

### 複数のファイルを 1 回で読み込むサンプル（ディレクトリ丸ごとの読み込み含む）

```golang
package main

import (
	"embed"
	"encoding/json"
	"fmt"
	"runtime"
)

// 複数データの埋め込み。
// パスのデータを同じ構成で"myRawDatas"に読み込む。この時点では raw（生）データのまま。
//
//go:embed all:assets
//go:embed data/sample.json
//go:embed foo.json
//go:embed bar.json
var myRawDatas embed.FS

// JSON データの構造を定義しておく
type TData struct {
	Name string `json:"name"`
}

func main() {
	// Go バージョンの確認
	fmt.Printf("Go version: %v\n", runtime.Version())

	// アセットに埋め込まれたディレクトリを参照する
	{
		myRawData1, _ := myRawDatas.ReadFile("assets/sample1.txt")
		fmt.Println("assets/sample1.txt:", string(myRawData1))

		myRawData2, _ := myRawDatas.ReadFile("assets/sample2.txt")
		fmt.Println("assets/sample2.txt:", string(myRawData2))
	}

	// 埋め込まれたデータから JSON としてパース（Unmarshal）する
	{
		d := TData{}
		myRawData, _ := myRawDatas.ReadFile("data/sample.json")
		json.Unmarshal([]byte(myRawData), &d)

		fmt.Printf("sample.json: %v\n", d.Name)
	}

	{
		d := TData{}
		myRawData, _ := myRawDatas.ReadFile("foo.json")
		json.Unmarshal([]byte(myRawData), &d)

		fmt.Printf("foo.json: %v\n", d.Name)
	}

	{
		d := TData{}
		myRawData, _ := myRawDatas.ReadFile("bar.json")
		json.Unmarshal([]byte(myRawData), &d)

		fmt.Printf("bar.json: %v\n", d.Name)
	}
	// Output:
	// assets/sample1.txt: Sample text 1.
	//
	// assets/sample2.txt: Sample text 2.
	//
	// Go version: go1.21.4
	// sample.json: hoge
	// foo.json: foo
	// bar.json: bar
}

```

```text:./assets/sample1.txt
Sample text 1.
```
```text:./assets/sample2.txt
Sample text 2.
```

```json:./data/sample.json
{
    "name": "hoge"
}
```

```json:./foo.json
{
    "name": "foo"
}
```

```json:./bar.json
{
    "name": "bar"
}
```


- [オンラインで動作をみる](https://play.golang.org/p/drx3b-glZta) @ Go Playground
- 動作確認バージョン: Go version 1.16