何があったか
Goでコーディング中、双方向リストっぽいものを自作&JSONに変換する必要性があった。方針としては encoding/json
を使っていくつもりが、ごちゃごちゃ詰まってしまった、という話。
要するに
- structに自分自身がいるときはちゃんとポインタにしましょう
- JSONに変換するとき、そいつが循環する可能性があるなら、
json:"-"
で無視させて、阻止しておこうね
structに自分自身がいると invalid recursive type
当たり前っちゃ当たり前の話ですが。
失敗編
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string
Parent Person
Child Person
}
func main() {
sora := Person{}
sora.Name = "Sora Amamiya"
j, err := json.Marshal(sora)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(j))
}
こう怒られます。
$ go run main.go
# command-line-arguments
./main.go:9:6: invalid recursive type Person
そりゃそうじゃ、という感じ。相互再帰でも怒ってくれるらしいです。優秀。
成功編
ポインタに直して解決。
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string
Parent *Person
Child *Person
}
func main() {
sora := Person{}
sora.Name = "Sora Amamiya"
j, err := json.Marshal(sora)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(j))
}
$ go run main.go
{"Name":"Sora Amamiya","Parent":null,"Child":null}
JSON循環阻止
本題です。
そもそも双方向リストをJSONに変えたいと思うことがどれだけあるのかが疑問ではありますが。
Person
の構造は先程と同じです。こんな感じに小さめの双方向リストを組みます。
失敗編
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name"`
Parent *Person
Child *Person `json:"child"`
}
func main() {
m := Person{}
n := Person{}
m.Name = "Momo Asakura"
m.Child = &n
n.Name = "Shiina Natsukawa"
n.Parent = &m
j, err := json.Marshal([]Person{m, n})
if err != nil {
log.Fatal(err)
}
fmt.Println(string(j))
}
出力はこんな感じで文字通り無限に怒られます。再帰エラーですね。
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
runtime stack:
runtime.throw(0x4f30b2, 0xe)
/usr/local/go/src/runtime/panic.go:774 +0x72
runtime.newstack()
/usr/local/go/src/runtime/stack.go:1046 +0x6e9
runtime.morestack()
/usr/local/go/src/runtime/asm_amd64.s:449 +0x8f
goroutine 1 [running]:
encoding/json.stringEncoder(0xc00007c000, 0x4c8f80, 0xc00000c080, 0x198, 0x100)
/usr/local/go/src/encoding/json/encode.go:589 +0x389 fp=0xc02009a380 sp=0xc02009a378 pc=0x4a94c9
encoding/json.structEncoder.encode(0xc000082000, 0x3, 0x4, 0xc00005e2a0, 0xc00007c000, 0x4dcee0, 0xc00000c
そりゃまあ当たり前ですね。「ParentにJSONタグつけなければワンチャン無視してくれないかな?」とかいう浅はかな考えをしたのがダメ。デフォルトでフィールド名をそのまま使って変換してくれます。非常に優秀なライブラリ。
成功編
つまり私がやりたかったことは「JSONライブラリで任意のフィールドを無視する」ということ。
そのためにはタグを json:"-"
とします。
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name"`
Parent *Person `json:"-"`
Child *Person `json:"child"`
}
func main() {
m := Person{}
n := Person{}
m.Name = "Momo Asakura"
m.Child = &n
n.Name = "Shiina Natsukawa"
n.Parent = &m
j, err := json.Marshal([]Person{m, n})
if err != nil {
log.Fatal(err)
}
fmt.Println(string(j))
}
出力結果はこちら。ちゃんとChildだけ持ってってくれてます。Rootから引っ張るならParentは別に出す必要ないですからね。
$ go run main.go
[{"name":"Momo Asakura","child":{"name":"Shiina Natsukawa","child":null}},{"name":"Shiina Natsukawa","chil
d":null}]
まとめ
- Go、書きやすくて素敵。
- JSONに変えやすくて素敵。
- ポインタ意識しなくて良い言語を使ってる人だけはポインタ学ぶなり思い出すなりしようね。
- みんなGoやろう、楽しいよ(初心者並感)。