Waka blog.

[Go]型埋め込み使用時のjson.Marshalの罠

型埋め込みを使用している構造体をjson.MarshalでJSON文字列に変換する際、意図しない挙動になるケースがあるので気を付ける。

下記のような構造体のインスタンスをjson.Marshalで文字列に変換する処理がある。

type Event struct {
	ID int
	time.Time // <- 埋め込みフィールド
}

event := Event{
	ID: 1234,
	Time: time.now(), // <- 構造体のインスタンス化時、埋め込みフィールドの名前は構造体の名前(=Time)
}

buf, _ := json.Marshal(event)
fmt.Println(string(str))

Event構造体をそのまま変換すると、以下のようなJSON文字列になることを期待しがち。

{"ID":1234,"Time":"2024-11-22T04::00:00.000000"}

しかし実際には、出力する文字列は以下のように現在時刻だけが出力される。

"2024-11-22T04::00:00.000000"

json.Marshalは対象の型がjson.Marshalerインタフェースを実装することでデフォルトのマーシャル動作を上書きすることが可能。

今回のケースではtime.Time型が時刻を返すMarshalJSONを実装しており、かつ、型埋め込みでプロモートされた結果、Event構造体のマーシャル動作が書き換わっていたのが原因だった。

回避策は以下の2つ

  1. 型埋め込みを辞めて名前を追加する
    type Event struct {
    	ID int
    	Time time.Time // <- 名前を追加することでtime.Timeは埋め込み型でなくなる
    }
  2. Event構造体に独自のjson.Marshalerを実装する
    func (e Event) MarshalJSON() ([]byte, error) {
    	return json.Marshal {
    		struct {
    			ID: int
    			Time time.Time
    		} {
    			ID: e.ID,
    			Time: e.Time,
    		}
    	}
    }