【Golang】PHP の serialize のようにオブジェクトを保存可能なデータにしたい【gob】

<h1>Go でオブジェクトをシリアライズ（シリアル化）したい</h1>

> Go 言語（以下 Golang）で、オブジェクトを保存 & 読み込みできるように[シリアライズ](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA)したい。

JSON 文字列に変換してもいいのですが、`json.Marshaler` や `json.Unmarshaler` インターフェースを実装しないといけないものだと面倒です。

特に、ユーザーが保存データを直接編集する必要もないし、他のプログラム言語とのやりとりもない場合に、<font color="darkorange">**PHP の <code><a href="https://www.php.net/manual/ja/function.serialize.php">serialize</a></code> くらい簡単に保存可能な値に変換できないものか**</font>悩みます。

[「"golang" phpのserializeみたいなもの」でググって](https://www.google.com/search?q=%22golang%22+php%E3%81%AEserialize%E3%81%BF%E3%81%9F%E3%81%84%E3%81%AA%E3%82%82%E3%81%AE)もドンピシャの記事が出てこなかったので、自分のググラビリティとして。

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

1. Golang には `gob` という自己表記型のデータ形式がある。（Go v1 から実装済み）
1. <code><a href="https://pkg.go.dev/encoding/gob" target="_blank">encoding/gob</a></code> パッケージの `Encode` でシリアル化、`Decode` で復元できる。
1. Golang 同士でしか使えないが、バイト・データなのでサイズは小さく済む。

- 参考文献
    - [Gobs of data](https://go.dev/blog/gob) | The Go Blog @ go.dev
    - [データのやりとりに gob を使う](https://qiita.com/delphinus/items/67a796cb7876132a1ec0) @ Qiita
    - [encoding/gob](https://pkg.go.dev/encoding/gob) | Standard library @ pkg.go.dev
    - [Protocol Buffers - Google's data interchange format](https://github.com/protocolbuffers/protobuf) @ GitHub
    - [Key-Value 型のデータ](https://www.google.com/search?q=Key-Value+store+database)を保存する際に、統一したインターフェースで、さまざまな保存先に保存・読み込みをしたい場合の便利なパッケージ。（`file` 保存で `JSON` や `Gob` を選択できる）
        - [github.com/philippgille/gokv](https://github.com/philippgille/gokv) @ GitHub

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

### シンプル・バージョン

```go:main.go
package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
)

type SimpleData struct {
	ID      int64
	Message string
}

func main() {
	dataOrig := SimpleData{ID: 123, Message: "my message"}
	fmt.Printf("Orig: %#v\n", dataOrig)

	enc, err := EncodeToGob(dataOrig)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Enc : %x\n", enc)

	dec, err := DecodeFromGob(enc)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Dec : %#v\n", *dec)
	// Output:
	// Orig: main.SimpleData{ID:123, Message:"my message"}
	// Enc : 2a7f0301010a53696d706c654461746101ff800001020102494401040001074d657373616765010c00000012ff8001fff6010a6d79206d65737361676500
	// Dec : main.SimpleData{ID:123, Message:"my message"}
}

func EncodeToGob(simpDat SimpleData) ([]byte, error) {
	buf := bytes.NewBuffer(nil)

	if err := gob.NewEncoder(buf).Encode(&simpDat); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

func DecodeFromGob(gobData []byte) (*SimpleData, error) {
	var simpDat SimpleData

	buf := bytes.NewBuffer(gobData)

	if err := gob.NewDecoder(buf).Decode(&simpDat); err != nil {
		return nil, err
	}

	return &simpDat, nil
}
```

- [オンラインで動作を見る](https://go.dev/play/p/bbgQ9ZGlv54) @ GoPlayground

### いささか複雑バージョン

```go:main.go
package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
)

// ----------------------------------------------------------------------------
//  Main
// ----------------------------------------------------------------------------

func main() {
	// 保存したいオブジェクトの作成
	apple1 := Fruit{
		Name:  "apple",
		Color: Red,
	}
	fmt.Println(apple1)

	// オブジェクトを保存可能なバイト・データに変換
	buf := bytes.NewBuffer(nil)
	err := gob.NewEncoder(buf).Encode(&apple1)
	ExitOnError(err)

	dataToSave := buf.Bytes() // 保存するバイト・データ

	fmt.Println("Saved data:", dataToSave)

	// 保存されたバイトデータからオブジェクトを復元
	var apple2 Fruit // 復元先の空のオブジェクト

	buf = bytes.NewBuffer(dataToSave)
	err = gob.NewDecoder(buf).Decode(&apple2)
	ExitOnError(err)

	fmt.Println(apple2)

	// Output:
	// Before: I am apple. My color is red.
	// Saved data: [38 255 129 3 1 1 5 70 114 117 105 116 1 255 130 0 1 2 1 4 78 97 109 101 1 12 0 1 5 67 111 108 111 114 1 4 0 0 0 12 255 130 1 5 97 112 112 108 101 1 2 0]
	// After: I am apple. My color is red.
}

// ----------------------------------------------------------------------------
//  Type: Color
// ----------------------------------------------------------------------------

// Color は色情報を保持する enum 用の型です。
type Color int

const (
	Unknown Color = iota // Unknown は不明な色を表す enum 定数です。
	Red                  // Red は赤色を表す enum 定数です。
	Green                // Green は緑色を表す enum 定数です。
	Yellow               // Yellow は黄色を表す enum 定数です。
)

// String は fmt.Stringer インターフェースの実装です。自身の色を示す文字列を返します。
func (c Color) String() string {
	switch c {
	case Red:
		return "red"
	case Green:
		return "green"
	case Yellow:
		return "yellow"
	}

	return "unknown"
}

// ----------------------------------------------------------------------------
//  Type: Fruit
// ----------------------------------------------------------------------------

// Fruit はフルーツの情報を保持する型です。
type Fruit struct {
	Name  string
	Color Color
}

// String は fmt.Stringer インターフェースの実装です。自身を示す文字列を返します。
func (f Fruit) String() string {
	return fmt.Sprintf("I am %s. My color is %s.", f.Name, f.Color)
}

// ----------------------------------------------------------------------------
//  Functions
// ----------------------------------------------------------------------------

// ExitOnError は err が nil でない場合に、エラーで終了します。
func ExitOnError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
```

- [オンラインで動作をみる](https://go.dev/play/p/7rPjIDx-oaU) @ GoPlayground