Go言語で、できるだけお手軽かつ安全にシリアライズ⇒デシリアライズしてみる

#やりたいこと

Goでオブジェクトをシリアライズして一時的に string に保存した後に、デシリアライズして元のオブジェクトに戻したいことがちょこちょこありまして。
お手軽にやるには encoding/json を使うのが簡単だろうということで色々と試してみたところ、元の構造体にちゃんとデシリアライズしようとすると、

* フィールドの定義にJSONフィールド名のタグがついている構造体を使う
* 構造体のフィールドに interface{} がない。（全てのフィールドの型が決まっている）
* （他にもあるかも）

の条件を満たしている必要がありました。
しかし、json.Marshal()/json.Unmarshal()はそんなことは関係なくゆるふわに扱えてしまうので、安全に元に戻せるよう、ちょっと制限を設けたいと思い試行錯誤してみました。

# 作ってみた

ということで、以下のような serialize パッケージを作ってみました。

## serialize パッケージ

```go:serialize/serialize.go
package serialize

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strings"
)

// serialize が返すエラーの定義
var (
	ErrorTypeUnmatch = errors.New("Type Unmatch")
)

// Serializable はシリアライズ可能な構造体であることを示すinterfaceです。
type Serializable interface {
	Serializable()
}

// Serialize はシリアライズを行います。
func Serialize(obj Serializable) (string, error) {
	var serializeStr string
	bytes, err := json.Marshal(obj)
	if err != nil {
		return serializeStr, err
	}
	serializeStr = fmt.Sprintf("%s\t%s", reflect.TypeOf(obj).String(), string(bytes))
	return serializeStr, nil
}

// Deserialize はデシリアライズを行います。
func Deserialize(serializeStr string, resObj Serializable) error {
	typeStr := reflect.TypeOf(resObj)
	strs := strings.SplitN(serializeStr, "\t", 2)
	if strs[0] != typeStr.String() {
		return ErrorTypeUnmatch
	}
	err := json.Unmarshal([]byte(strs[1]), resObj)
	if err != nil {
		return err
	}
	return nil
}
```

##使用方法

使い方は以下のような感じです。
安全にシリアライズできる構造体に Serializable() を実装します。（空関数でOK）

```go:
// Struct1 はテスト用の構造体1です。
type Struct1 struct {
	Field1 string            `json:"field1"`
	Field2 []string          `json:"field2"`
	Field3 map[string]string `json:"field3"`
}

// Serializable はシリアライズ可能であることを示します。
func (s Struct1) Serializable() {}

// Struct2 はテスト用の構造体2です。
type Struct2 struct {
	Field1 string  `json:"field1"`
	Field2 Struct3 `json:"field2"`
}

// Serializable はシリアライズ可能であることを示します。
func (s Struct2) Serializable() {}

// Struct3 はテスト用の構造体3です。
type Struct3 struct {
	Field1 []string `json:"field1"`
}

// Serializable はシリアライズ可能であることを示します。
func (s Struct3) Serializable() {}
```

serialize.Serialize() でシリアライズを行います。
これに渡せるのはSerializable interfaceを実装した構造体のみです。

```go:
	// Struct1 のシリアライズ
	a := Struct1{Field1: "test", Field2: []string{"1", "2"}, Field3: map[string]string{"test2": "3"}}
	str, err := serialize.Serialize(&a)
	if err != nil {
		fmt.Println(err)
	}
```

シリアライズ結果は以下のようになります。
JSONの前にタブ区切りで型名が入っています。

```
*main.Struct1	{"field1":"test","field2":["1","2"],"field3":{"test2":"3"}}
```

デシリアライズは serialize.Deserialize() で行います。

```go:
	// Struct1 のデシリアライズ
	b := Struct1{}
	err = serialize.Deserialize(str, &b)
	if err != nil {
		fmt.Println(err)
	}
```

以下のようにフィールドに別の構造体がある場合も大丈夫です。
（構造体のフィールドの型が全て決まっていれば）

```go:
	// Struct2 (Struct3をフィールドとして持つ)のシリアライズ
	a := Struct2{Field1: "ggg", Field2: Struct3{Field1: []string{"kkk", "lll"}}}
	str, err := serialize.Serialize(&a)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("Struct2 のシリアライズ結果 = " + str)

	// Struct2 のデシリアライズ
	b := Struct2{}
	err = serialize.Deserialize(str, &b)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("Struct2 のデシリアライズ結果 = %#v\n", b)
```

結果は以下のようになります。

```
Struct2 のシリアライズ結果 = *main.Struct2	{"field1":"ggg","field2":{"field1":["kkk","lll"]}}
Struct2 のデシリアライズ結果 = main.Struct2{Field1:"ggg", Field2:main.Struct3{Field1:[]string{"kkk", "lll"}}}
```

違う型にデシリアライズしようとした場合には ErrorTypeUnmatch error を返します。 

```go:
	// Struct1 のシリアライズ
	a := Struct1{Field1: "test", Field2: []string{"1", "2"}, Field3: map[string]string{"test2": "3"}}
	str, err := serialize.Serialize(&a)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println("Struct1 のシリアライズ結果 = " + str)

	// Struct2 に Struct1 をデシリアライズしようとしてみる
	b := Struct2{}
	err = serialize.Deserialize(str, &b)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("Struct1 のデシリアライズ結果 = %#v\n", b)
```

結果は以下のようになります。

```
Struct1 のシリアライズ結果 = *main.Struct1	{"field1":"test","field2":["1","2"],"field3":{"test2":"3"}}
Type Unmatch
```

まだまだ使い勝手に難ありそうですが、ひとまず自作で使いながら改良していこうと思います。

# 今回使用したコードについて

今回使用したコードの全文は以下のGistに置いてあります。
https://gist.github.com/yoshinoyaussie/9589a70dced6995637fe841ff9036f87