私の知らない go 言語: 構造体の埋め込み

https://github.com/nabetani/marshaljsonf64
を書くに当たって気がついた go と json.Marshal / json.Unmarshal の仕様をいくつか紹介する。

## 組み込み型を埋め込む

```go
package main

import "fmt"

type t0 struct {
	int
}

func main() {
	v0 := t0{int: 123} // "int:" で初期化する
	//v0 = 456 // エラー
	v0.int = 456    // こう書く
	fmt.Println(v0) //=> "{456}"
}
```

`type foo int` と `type foo struct{ int }` は似ている。

後者を使うべき理由に心当たりはない。

## ポインタを埋め込む

```go
package main

type t10 struct {
	*int
}

type t11 struct {
	**int // "unexpected *, expecting name" というエラーになる
}

type pint *int
type t12 struct {
	pint // "embedded type cannot be a pointer" というエラーになる
}

func main() {
	vi := 1
	v10 := t10{int: &vi} // "int:" で初期化する
	*v10.int = 3         // こう書く。「*v10=3」とは書けない
}
```

`t10` が ok で、 `t12` がエラーなのは、かなり思いがけなかった。

## 両方埋め込む

```go
package main

type t20 struct {
	int
	*int // "duplicate field int" というエラーになる
}
```

まあそうだよね。

## 配列・スライス・map を埋め込む

```go
package main

type t30 struct {
	[1]int // "unexpected [, expecting field name or embedded type"
}

type t31 struct {
	[]int // "unexpected [, expecting field name or embedded type"
}

type t32 struct {
	map[int]int // "syntax error: unexpected map, expecting field name or embedded type"
}

type a [1]int
type s []int
type m map[int]int

type t30x struct{ a } // ok
type t31x struct{ s } // ok
type t32x struct{ m } // ok

func main() {
	v30 := t30x{a: [1]int{12}}
	v30[0] = 123 // "invalid operation: v30[0] (type t30x does not support indexing)"
	v30.a[0] = 456

	v31 := t31x{s: []int{34}}
	v31[0] = 345 // "invalid operation: v31[0] (type t31x does not support indexing)"
	v31.s[0] = 678

	v32 := t32x{m: map[int]int{56: 78}}
	v32[0] = 99 // invalid operation: v32[0] (type t32x does not support indexing)
	v32.m[0] = 99
}
```

そのままでは埋め込めない。アクセスする名前がなくて困るからだと思う。
名前をつけると埋め込める。
indexing はメソッドじゃないのでそのままでは呼べない。

## 何重にも埋め込む

```go
package main

type t40 struct{ foo int }
type t41 struct{ t40 }  // そのまま埋め込む
type t42 struct{ *t41 } // ポインタを埋め込む

func main() {
	v40 := t40{1}
	v41 := t41{v40}
	t42 := t42{&v41}
	t42.foo = 3 // 何段階も下れる
}
```

ポインタ埋め込みとそのまま埋め込みを混在させることができる。
混在していても一気に一番奥のメンバにアクセスできる

## 自分を埋め込む

ポインタを埋め込めるので、自分自身を埋め込むことができる。

```go
package main

type T50 struct {
	*T50
	Foo string
}

func main() {
	a := T50{T50: nil, Foo: "a"}
	b := T50{T50: nil, Foo: "b"}
	c := T50{T50: nil, Foo: "c"}
	a.T50 = &a
	b.T50 = &c
	c.T50 = &b
}
```

有意義な使いみちの心当たりはない。

## 埋め込みとJSON ― 名前の重複

埋め込み構造体を JSON にすると、そのままフラットに展開される。
じゃあ、同じメンバ名を持つ構造体を埋め込んだらどうなるか。

* 実行時エラーになる(好ましい)
* 最初に見つかった方だけが出る(まあまあ酷い仕様)
* 同名のキーで出てきてしまう(かなり酷い仕様)

のいずれかだと予想したんだけど、予想は外れた。

```go
package main

import (
	"encoding/json"
	"fmt"
)

type t70 struct {
	Foo string
	Bar string
}

type t71 struct {
	Foo string
	Baz string
}

type t72 struct {
	t70
	t71
}

type t70x struct {
	t70
}
type t71x struct {
	t71
}
type t74 struct {
	t70x
	t71
}

func asjson(i interface{}) {
	j, e := json.Marshal(i)
	if e != nil {
		fmt.Println("err:", e)
		return
	}
	fmt.Println("json:", string(j))
}

func main() {
	v70 := t70{"70.foo", "70.bar"}
	v71 := t71{"71.foo", "71.baz"}
	asjson(v70)
	//=> json: {"Foo":"70.foo","Bar":"70.bar"}

	asjson(v71)
	//=> json: {"Foo":"71.foo","Baz":"71.baz"}

	asjson(t72{v70, v71})
	//=> json: {"Bar":"70.bar","Baz":"71.baz"}

	asjson(t74{t70x{v70}, v71})
	//=> json: {"Bar":"70.bar","Foo":"71.foo","Baz":"71.baz"}
}
```

正解は、

* **同名のフィールドがある場合はそのフィールドは無視される**(かなり酷い仕様)

だった。しかも `t74` の例のとおり

* ただし、深さが違う場合は浅い方優先

だとおもう。

びっくりした。

ちなみに。

* Unmarshal でも無視される。
* 一方に `json:"Foo"` を付与すると、そっちが勝って、ついてないほうが無視される。
* 両方に `json:"Foo"` を付与すると、両方無視される。

## 埋め込みとJSON ― 名前がつく場合とつかない場合

```go
package main

import (
	"encoding/json"
	"fmt"
)

type T80 []int
type T81 struct{ Foo int }
type T82 struct{ Bar int }
type T83 struct{ Baz int }
type T84 T83
type T85 struct {
	T80
	T81
	T82 `json:"T82"`
	T84
}

func asjson(i interface{}) {
	j, e := json.Marshal(i)
	if e != nil {
		fmt.Println("err:", e)
		return
	}
	fmt.Println("json:", string(j))
}

func main() {
	v := T85{
		T80: T80{1, 2},
		T81: T81{34},
		T82: T82{56},
		T84: T84{78},
	}
	asjson(v)
	//=> json: {"T80":[1,2],"Foo":34,"T82":{"Bar":56},"Baz":78}
}
```

int や スライスのような型を埋め込む場合と、構造体を埋め込む場合で動作が違う。

* int や スライスのような型は、型名がキー名になる
* `json:"hoge"` のようなものがついていれば、そのキー名がつく
* それ以外なら、親構造体のメンバにそのまま展開される

となる。

## 埋め込みとJSON ― ポインタを埋め込む場合

```go
package main

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

type T90 struct {
	Foo int
	Bar int
}
type T91 struct {
	Baz int
	Qux int
}
type T92 struct {
	*T90
	*T91
}

func asjson(i interface{}) {
	j, e := json.Marshal(i)
	if e != nil {
		fmt.Println("err:", e)
		return
	}
	fmt.Println("json:", string(j))
}

func t92FromJSON(j string) {
	v := T92{}
	e := json.Unmarshal([]byte(j), &v)
	if e != nil {
		fmt.Println("err:", e)
		return
	}
	fmt.Println(strings.Replace(fmt.Sprintf("val: %_", v), "%!_", "", -1))
}

func main() {
	asjson(T92{T90: &T90{12, 34}, T91: nil})
	//=> json: {"Foo":12,"Bar":34}
	
	asjson(T92{T90: &T90{12, 34}, T91: &T91{56, 78}})
	//=> json: {"Foo":12,"Bar":34,"Baz":56,"Qux":78}
	
	t92FromJSON(`{"Foo":12}`)
	//=> val: {(*main.T90=&{12 0}) (*main.T91=<nil>)}
	
	t92FromJSON(`{"Foo":12,"Baz":34}`)
	//=> val: {(*main.T90=&{12 0}) (*main.T91=&{34 0})}
	
}
```

* 埋め込まれているポインタが nil だと、そのフィールドは Marshal されない。
* Unmarshal 時には、必要に応じてオブジェクトが作られる。

ということになっているようだ。
埋め込まれているオブジェクトが nil かどうかで JSON のキーの数が変わるのはちょっと意外だった。

## 最後に

まだ書いてないこともちょっとあるんだけど、今日のところはこれぐらいで。