かなり初歩的な理解不足だったと思うのですが、忘れないために備忘録として残します。
TL;DR
スライスの宣言だけと初期化は厳密には違い、nil sliceと空sliceの挙動が違うよという話
何をしてしまったのか
以下のようなことを実現するつもりでした。
- 特定のスライスとスライスを組み合わせて、一つの変数(hogeList)にしてレスポンスを返す
- 空だった場合は
{"List":[]}
でjsonで返却する
echoを使った簡単なイメージは以下の通りです。
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
type (
Hoge struct {
ID int `json:"id"`
Name string `json:"name"`
}
HogeResponse struct {
HogeList []Hoge `json:"hogeList"`
}
)
func main() {
e := echo.New()
e.GET("/api/hoge", func(c echo.Context) error {
hogeList := HogeResponse{
HogeList: []Hoge{},
}
return c.JSON(http.StatusOK, hogeList)
})
e.Logger.Fatal(e.Start(":8080"))
}
これはHogeResponseの変数を定義してHogeListも空として定義したので
想定通り空リストが返却されました。
{
"hogeList": []
}
例えばこれにデータを入れて返すとします。
先ほどのコードを少しいじりました。
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
type (
Hoge struct {
ID int `json:"id"`
Name string `json:"name"`
}
HogeResponse struct {
HogeList []Hoge `json:"hogeList"`
}
// 取得した加工したいデータとする
FugaData struct {
ID int `json:"id"`
Name string `json:"name"`
Place string `json:"place"`
Address string `json:"address"`
}
FugaList []FugaData
)
func main() {
e := echo.New()
e.GET("/api/hoge", func(c echo.Context) error {
hogeList := HogeResponse{
HogeList: []Hoge{},
}
// データを取得したとします
data := []FugaData{
{ID: 1, Name: "hoge1", Place: "tokyo", Address: "tokyo"},
{ID: 2, Name: "hoge2", Place: "longdon", Address: "longdon"},
{ID: 3, Name: "hoge3", Place: "paris", Address: "paris"},
}
// データをhogeとして格納する
var ret []Hoge
for _, d := range data {
ret = append(ret, Hoge{
ID: d.ID,
Name: d.Name,
})
}
// 格納したhogeを元のデータに格納する
hogeList.HogeList = ret
return c.JSON(http.StatusOK, hogeList)
})
e.Logger.Fatal(e.Start(":8080"))
}
これを動かすと以下のようにデータが取得できます。まあ普通ですね。
{
"hogeList": [
{
"id": 1,
"name": "hoge1"
},
{
"id": 2,
"name": "hoge2"
},
{
"id": 3,
"name": "hoge3"
}
]
}
で、問題はここでした。
ケースによっては、fugaデータがない場合があり、その場合は想定通り
Listは空のリストで返却したいです。
以下のように変更してみました。
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
type (
Hoge struct {
ID int `json:"id"`
Name string `json:"name"`
}
HogeResponse struct {
HogeList []Hoge `json:"hogeList"`
}
// 取得した加工したいデータとする
FugaData struct {
ID int `json:"id"`
Name string `json:"name"`
Place string `json:"place"`
Address string `json:"address"`
}
FugaList []FugaData
)
func main() {
e := echo.New()
e.GET("/api/hoge", func(c echo.Context) error {
hogeList := HogeResponse{
HogeList: []Hoge{},
}
// データはありません
data := []FugaData{}
// データをhogeとして格納する
var ret []Hoge
// 回すデータはないので、このループは空振ります
for _, d := range data {
ret = append(ret, Hoge{
ID: d.ID,
Name: d.Name,
})
}
// 格納したhogeを元のデータに格納する
hogeList.HogeList = ret
return c.JSON(http.StatusOK, hogeList)
})
e.Logger.Fatal(e.Start(":8080"))
}
とループが回らないように変更するとレスポンスが変わりました。
{
"hogeList": null
}
おおぅ..と思いました。
てっきり、var ret []Hoge
としていたので、このretは少なくともスライスが定義されていると
考えていました。
(完全にPHP脳でした)
どうやらスライスの変数は、宣言したか・初期化したかで挙動が変わってしまうようです。
今回のケースは宣言だけだったのでnil sliceとなっていたようでした。
空スライスに変更してみます。
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
type (
Hoge struct {
ID int `json:"id"`
Name string `json:"name"`
}
HogeResponse struct {
HogeList []Hoge `json:"hogeList"`
}
// 取得した加工したいデータとする
FugaData struct {
ID int `json:"id"`
Name string `json:"name"`
Place string `json:"place"`
Address string `json:"address"`
}
FugaList []FugaData
)
func main() {
e := echo.New()
e.GET("/api/hoge", func(c echo.Context) error {
hogeList := HogeResponse{
HogeList: []Hoge{},
}
// データはありません
data := []FugaData{}
// データをhogeとして格納する
ret := []Hoge{} // 初期化に変更
// 回すデータはないので、このループは空振ります
for _, d := range data {
ret = append(ret, Hoge{
ID: d.ID,
Name: d.Name,
})
}
// 格納したhogeを元のデータに格納する
hogeList.HogeList = ret
return c.JSON(http.StatusOK, hogeList)
})
e.Logger.Fatal(e.Start(":8080"))
}
{
"hogeList": []
}
すごい初歩的なことですが、あまり意識して使ってなかったなと反省しました。
参考