【Go】MarshalとUnmarshalを使ったエンコーディング

Go

今回は、Package jsonで定義されているMarshalとUnmarshalについて確認していきます。

アプリケーションを作成する際、JSON形式のデータを扱うことは良くあります。MarshalとUnmarshalは、JSON形式のデータを扱う際に便利な関数です。Unmarshalを使うことで受け取ったJSON形式のデータをGoのstructに割り当てることができ、Marshalを使うことでstructからJSON形式のデータに簡単に変換することができます。

それでは、MarshalとUnmarshalについて確認していきます。

Unmarshal

func Unmarshal(data []byte, v interface{}) error

Unmarshalは、JSON形式で受け取った値を指定したポインタに保存させることができます。第1引数にJSON形式のデータを、第2引数にポインタを指定します。

第2引数の値がnilもしくはポインタではない場合、InvalidUnmarshalErrorを返します。

type Person struct {
	Name      string
	Age       int
	Nicknames []string
}

func main() {
	b := []byte(`{"Name":"mike", "Age":20, "nicknames":["a", "b", "c"]}`)
	var p Person
	if err := json.Unmarshal(b, &p); err != nil {
		fmt.Println(err)
	}
	fmt.Println(p.Name, p.Age, p.Nicknames)
}

上記のようなコードがあると仮定します。

% go run main.go
mike 20 [a b c]

Marshal

func Marshal(v interface{}) ([]byte, error)

Marshalは、引数の内容をJSON形式にエンコーディングした値を返します。

type Person struct {
	Name      string
	Age       int
	Nicknames []string
}

func main() {
	b := []byte(`{"Name":"mike", "Age":20, "nicknames":["a", "b", "c"]}`)
	var p Person
	if err := json.Unmarshal(b, &p); err != nil {
		fmt.Println(err)
	}
	fmt.Println(p.Name, p.Age, p.Nicknames)

	v, _ := json.Marshal(p)
	fmt.Println(string(v))
}

先程のコードにMarshalの記述を追加しました。上記のコードを実行すると以下のような結果が得られます。

% go run main.go
mike 20 [a b c]
{"Name":"mike","Age":20,"Nicknames":["a","b","c"]}

JSON形式の値が返って来ていることが確認できます。先程、Personというstructのポインタに対して、Unmarshalを使ってデータを保存しました。その保存した内容を、Person structのキー、バリューの関係性を考慮して出力してくれています。

現在、Marshalを使っってJSON形式で出力をしましたが、キーの名前が大文字になっています。JSON形式でデータを扱う場合、大文字になることはあまり無いため小文字にしたいです。小文字で指定をするには、structにタグを付けることで対応できます。

type Person struct {
	Name      string   `json:"name"`
	Age       int      `json:"age"`
	Nicknames []string `json:"nicknames"`
}
% go run main.go
{"name":"mike","age":20,"nicknames":["a","b","c"]}

Structはキャピタルの大文字ですが、タグをつけることでキーが小文字になっていることが確認できます。タグは他にも存在します。

type Person struct {
	Name      string   `json:"-"`
	Age       int      `json:"age,string"`
	Nicknames []string `json:"nicknames,omitempty"`
}

func main() {
	b := []byte(`{"Name":"mike", "Age":"20", "nicknames":[]}`)
	var p Person
	if err := json.Unmarshal(b, &p); err != nil {
		fmt.Println(err)
	}
	fmt.Println(p.Name, p.Age, p.Nicknames)

	v, _ := json.Marshal(p)
	fmt.Println(string(v))
}

上記のコードを実行すると以下のような結果が得られます。

% go run main.go
 20 []
{"age":"20"}

jsonに対して「」と指定することで、その値を隠すことができます。

また、「omitempty」と指定をすることで「値が存在しなければ隠す」ということができます。今回、スライスの中身を空にしているため、nicknamesは表示されていません。stringでは「””(空の文字列)」、intでは「0」の場合に非表示となります。

次に、タイプがstructの場合のomitemptyについて確認します。以下のようにコードを書き換えます。

type T struct{}

type Person struct {
	Name      string   `json:"-"`
	Age       int      `json:"age,string"`
	Nicknames []string `json:"nicknames,omitempty"`
	T         T        `json:"T,omitempty"`
}

//省略

v, _ := json.Marshal(p)
fmt.Println(string(v)

Tというstructを作成し、Personのstructにfieldとして追加します。そして、omitemptyのタグを追加します。しかし、上記のコードを実行してもTのfieldは表示されます。

% go run main.go
{"age":"20","T":{}}

そのため、structをomitemptyを使って非表示にしたい場合、ポインタ型にする必要があります。

type Person struct {
	Name      string   `json:"-"`
	Age       int      `json:"age,string"`
	Nicknames []string `json:"nicknames,omitempty"`
	T         *T       `json:"T,omitempty"`
}

すると、以下のようにTが非表示になります。

% go run main.go
 20 []
{"age":"20"}

Marshal・Unmarshalのカスタマイズ

Marshal・Unmarshalの処理内容はカスタマイズすることができます。

まずは、Marshalのカスタマイズに関して確認します。Marshalをカスタマイズする場合、MarshalJSON()という名前にする必要があります。

type T struct{}

type Person struct {
	Name      string   `json:"name,omitempty"`
	Age       int      `json:"age,string"`
	Nicknames []string `json:"nicknames,omitempty"`
}

func (p Person) MarshalJSON() ([]byte, error) {
	v, err := json.Marshal(&struct {
		Name string
	}{
		Name: "Mr." + p.Name,
	})
	return v, err
}

func main() {
	b := []byte(`{"Name":"Mike", "Age":"20", "nicknames":[]}`)
	var p Person
	if err := json.Unmarshal(b, &p); err != nil {
		fmt.Println(err)
	}

	v, _ := json.Marshal(p)
	fmt.Println(string(v))
}

上記のコードでは、json.Marshal()が呼ばれた際に、新しく定義したMarshalJSON()の内容が呼び出されるようになります。今回はNameを表示する際、最初に「Mr.」とつけるようにカスタマイズしました。そのため、コードを実行すると以下のような結果が得られます。

go run main.go
{"Name":"Mr.Mike"}

次に、Unmarshalのカスタマイズに関して確認します。Unmarshalをカスタマイズする場合、UnmarshalJSON()という名前にする必要があります。

func (p *Person) UnmarshalJSON(b []byte) error {
	type Person2 struct {
		Name string
	}
	var p2 Person2
	err := json.Unmarshal(b, &p2)
	p.Name = p2.Name + "!"
	return err
}

先程のコードに上記のコードを付け加えることで、json.Unmarshalが呼ばれた際にUnmarshalJSONが呼ばれるようになります。今回は、UnmarshalでJSONの内容をPersonのstructに保存する際、Nameに「!」を入れて保存するようにしました。そのため、ファイルを実行すると以下のような結果が得られます。

% go run main.go               
{"Name":"Mr.Mike!"}

参考

現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発

Package json

タイトルとURLをコピーしました