LoginSignup
2
2

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

Last updated at Posted at 2021-03-10

Golang で外部ファイルをコンパイル/ビルドしたバイナリに埋め込みたい

例えば JSON ファイルとか、画像とか。多言語の bind みたいな感じで。io/ioutil も微妙に使い勝手が悪いし、今後使えなくなる(deprecated になった)らしいし。知らんけど。

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

「golang ファイル 埋め込み サンプル」で Qiita 記事をググっても、ドンピシャのサンプルがなかったので、自分のググラビリティとして。

TL; DR (今北産業)

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

TS; DR (マスター、とりあえず動くものをくれ)

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

./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
data/sample.json
{
    "name": "hoge"
}

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

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

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

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

go:embed only allowed in Go files that import "embed" エラー

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

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

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

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
}

./assets/sample1.txt
Sample text 1.
./assets/sample2.txt
Sample text 2.
./data/sample.json
{
    "name": "hoge"
}
./foo.json
{
    "name": "foo"
}
./bar.json
{
    "name": "bar"
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2