GAE/Go Datastore ネストされた構造体の保存（さらにsliceを組み合わせた場合）

(2020.8.12 追記)
本記事はGAE/Go 1st genの `appengine/datastore` パッケージを使用した場合の内容となっています。
Client Library(`cloud.google.com/go/datastore`パッケージ)を使用した場合はデフォルトの挙動が異なります。Client Libraryの場合構造体タグに `flatten` を指定すると本記事と同様の挙動となります。

------

GAE/Go のDatastore APIはネストされた構造体を保存することが出来ます。
今回、構造体にsliceが絡んだ場合にどういうカタチで保存されるのかが興味あったので試してみました。

## 単一構造体ネスト

まずはシンプルに、構造体に構造体をネスト。

### 構造体定義
```go
type Inner struct {
	Name  string
	Value int
}

type Outer struct {
	Name  string
	Inner Inner
}
```

### 保存
```go
	c := appengine.NewContext(r)

	key := datastore.NewKey(c, "Outer", "test", 0, nil)
	outer := Outer{
		Name: "outer",
		Inner: Inner{
			Name:  "inner",
			Value: 999,
		},
	}

	if _, err := datastore.Put(c, key, &outer); err != nil {
		return err
	}
```

### 結果
<img width="700" alt="スクリーンショット 2017-05-24 20.35.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/31437/0cbba078-7f0f-b986-8fb6-b55a6f2c8b00.png">

`Inner.Name` `Inner.Value` というdot区切りの別々のプロパティに展開されます。

## 構造体のslice
構造体のsliceをフィールドとして持つケース。
### 構造体定義
```go
type Inner struct {
	Name  string
	Value int
}

type Outer struct {
	Name   string
	Inners []Inner
}
```

### 保存
```go
	key := datastore.NewKey(c, "Outer", "test", 0, nil)
	outer := Outer{
		Name: "outer",
		Inners: []Inner{
			Inner{
				Name:  "inner1",
				Value: 111,
			},
			Inner{
				Name:  "inner1",
				Value: 222,
			},
		},
	}

	if _, err := datastore.Put(c, key, &outer); err != nil {
		return err
	}

```

### 結果
<img width="700" alt="スクリーンショット 2017-05-24 20.41.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/31437/d65ee091-3da0-fbb8-5cb7-435c9644083e.png">

`Inners.Name` 、 `Inners.Value` それぞれがリストプロパティとして展開されました。（同じlengthになるということですね）

## ネストされた構造体の中にslice
ネストされた構造体がsliceフィールドを持つケースです。
### 構造体定義
```go
type Inner struct {
	Names  []string
	Values []int
}

type Outer struct {
	Name  string
	Inner Inner
}
```

### 保存
```go
	key := datastore.NewKey(c, "Outer", "test", 0, nil)
	outer := Outer{
		Name: "outer",
		Inner: Inner{
			Names:  []string{"inner1-1", "inner1-2"},
			Values: []int{11, 12, 13},
		},
	}

	if _, err := datastore.Put(c, key, &outer); err != nil {
		return err
	}
```

### 結果
<img width="695" alt="スクリーンショット 2017-05-24 20.55.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/31437/d1aa4837-7ebf-f935-601c-8efa0d0842ad.png">

ネストされた構造体sliceのケースと同じ形式で展開されました。この場合だとlengthは同じとは限りません。
なるほどよく出来てる。

## sliceの中にさらにslice
slice中構造体の中にさらにsliceがあった場合はどのように展開されるのでしょう？

### 構造体定義
```go
type Inner struct {
	Names  []string
	Values []int
}

type Outer struct {
	Name   string
	Inners []Inner
}
```

### 保存
```go
	key := datastore.NewKey(c, "Outer", "test", 0, nil)
	outer := Outer{
		Name: "outer",
		Inners: []Inner{
			Inner{
				Names:  []string{"inner1-1", "inner1-2"},
				Values: []int{11, 12},
			},
			Inner{
				Names:  []string{"inner2-1", "inner2-2"},
				Values: []int{21, 22, 23},
			},
		},
	}

	if _, err := datastore.Put(c, key, &outer); err != nil {
		return err
	}

```

### 結果
`datastore: flattening nested structs leads to a slice of slices: field "Inners"` というエラーになりました。
プロパティツリー中、異なる階層にsliceがあるとエラーになるのですね。

# まとめ
あまり複雑なデータ構造に使用するのは厳しそうですが、プロパティをグルーピングするくらいに使うにはとても便利そう＼(^o^)／



# 参考
https://cloud.google.com/appengine/docs/standard/go/datastore/reference#hdr-Structured_Properties