🐡

Goで、json.UnmarshalとMarshalとエンコード

2022/10/02に公開

Golangの、UnmarshalとMarshalについて解説してみたいと思います。
ネットワーク越しで、取ってきたデータをGoの構造体に変換したり、Goの構造体からJSONに変換してデータを送信したりと使用する機会も多いと思うので、是非参考にしていただければと思います。

Unmarshal

Unmarshalは、ネットワーク越しに送信されたデータをGoの構造体に変換します。

func Unmarshal(data []byte, v any) error

Unmarshalは、JSON形式で受け取った値を指定した構造体に格納することができます。第1引数にJSON形式のデータを、第2引数に格納したい構造体を指定します。
第2引数の値がnilもしくはポインタではない場合、InvalidUnmarshalErrorを返します。

main.go
package main

import (
	"encoding/json"
	"fmt"
)

type Book struct {
	Title     string
	Author    string
	Publisher string
}

func main() {
	b := []byte(`{"title": "リーダブルコード", "author": "Trevor Foucher", "Publisher": "OREILLY"}`)
	var book Book
	if err := json.Unmarshal(b, &book); err != nil {
		fmt.Println(err)
	}
	fmt.Println(book.Title, book.Author, book.Publisher)
}

実行してみます。

$ docker-compose up
go_1  | running...
go_1  | リーダブルコード Trevor Foucher OREILLY

ネットワークで入ってきた、byteのスライスをBook構造体のキーをみて変換してくれるのが、Unmarshalとなります。

Marshal

Goの構造体に格納したデータを、JSON形式に変換してネットワーク越しに送信したい場合に、Marshalを使用します。

func Marshal(v any) ([]byte, error)

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

main.go
package main

import (
	"encoding/json"
	"fmt"
)

type Book struct {
	Title     string
	Author    string
	Publisher string
}

func main() {
	b := []byte(`{"title": "リーダブルコード", "author": "Trevor Foucher", "Publisher": "OREILLY"}`)
	var book Book
	
	v, err := json.Marshal(book)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(v))
}

実行してみます。

$ docker-compose up
go_1  | running...
go_1  | {"Title":"リーダブルコード","Author":"Trevor Foucher","Publisher":"OREILLY"}

JSON形式の値が返って来ていることが確認できます。

Marshalを使ってJSON形式で出力をしましたが、キーの名前が大文字になっています。JSON形式でデータを扱う場合、大文字になることはあまり無いため小文字にしてみましょう。小文字で指定をするには、構造体にタグを付けることで対応できます。
Marshalするときには、どのような名前でエンコードするかを指定することができます。

main.go
type Book struct {
	Title     string `json:"title"`
	Author    string `json:"author"`
	Publisher string `json:"publisher"`
}

再度実行してみます。

$ docker-compose up
go_1  | running...
go_1  | {"title":"リーダブルコード","author":"Trevor Foucher","publisher":"OREILLY"}

構造体の頭文字は大文字ですが、タグをつけることでキーが小文字になっていることが確認できます。

データを隠す

このデータは表示させたくないというケースがあると思います。そのような場合は、JSONのタグにハイフンを指定することで、データを隠すことができます。

main.go
type Book struct {
	Title     string `json:"title"`
	Author    string `json:"-"`
	Publisher string `json:"publisher"`
}

実行してみます。

$ docker-compose up
go_1  | running...
go_1  | {"title":"リーダブルコード","publisher":"OREILLY"}

ハイフンを指定した、Authorが表示されていないのが確認できると思います。
著者名を隠すってどんなケースだよ!ですよね笑、題材が悪かったですね笑
パスワードとかkeyとかで使えるのかなと思います!

Marshalを独自カスタマイズ

Marshalを拡張させて、独自に処理を加えてカスタマイズすることもできます。便利ですね!
MarshalJSONの形で記述しないとカスタマイズすることができないです。Marshalが呼ばれた時に自動的にMarshalJSONメソッドが呼ばれる様になります。

main.go
package main

import (
	"encoding/json"
	"fmt"
)

type Book struct {
	Title     string `json:"title"`
	Author    string `json:"-"`
	Publisher string `json:"publisher"`
}

func (b Book) MarshalJSON() ([]byte, error) {
	v, err := json.Marshal(&struct {
		Publisher string
	}{
		Publisher: b.Publisher + "Japan",
	})
	return v, err
}

func main() {
	b := []byte(`{"title": "リーダブルコード", "author": "Trevor Foucher", "Publisher": "OREILLY"}`)
	var book Book
	if err := json.Unmarshal(b, &book); err != nil {
		fmt.Println(err)
	}
	fmt.Println(book.Title, book.Author, book.Publisher)

	v, err := json.Marshal(book)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(v))
}

実行してみます。

$ docker-compose up
go_1  | running...
go_1  | {"Publisher":"OREILLY Japan"}

OREILLYから、OREILLY Japanに変わってると思います。

Unmarshalを独自カスタマイズ

Marshalと同様にUnmarshalもUnmarshalJSONを使用すれば、カスタマイズできます。

main.go
package main

import (
	"encoding/json"
	"fmt"
)

type Book struct {
	Title     string `json:"title"`
	Author    string `json:"author"`
	Publisher string `json:"publisher"`
}

func (b *Book) UnmarshalJSON(byte []byte) error {
	type Book2 struct {
		Title string
	}
	var b2 Book2
	err := json.Unmarshal(byte, &b2)
	if err != nil {
		fmt.Println(err)
	}
	b.Title = b2.Title + "-より良いコードを書くためのシンプルで実践的なテクニック"
	return err
}

func main() {
	b := []byte(`{"title": "リーダブルコード", "author": "Trevor Foucher", "Publisher": "OREILLY"}`)
	var book Book
	if err := json.Unmarshal(b, &book); err != nil {
		fmt.Println(err)
	}
	fmt.Println(book.Title, book.Author, book.Publisher)

	v, err := json.Marshal(book)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(v))
}

実行してみます。

$ docker-compose up
go_1  | running...
go_1  | {"title":"リーダブルコード-より良いコードを書くためのシンプルで実践的なテクニック",

Marshalと同様しっかりカスタマイズできていますね!
いかがだったでしょうか、UnmarshalとMarshalは、使用頻度も高いと思いますので、少しでも理解の助けになればと思います!

Discussion