LoginSignup
0
0

More than 3 years have passed since last update.

Goで双方向リストをJSONに変換する際のつまずき

Posted at

何があったか

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 の構造は先程と同じです。こんな感じに小さめの双方向リストを組みます。

image.png

失敗編

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やろう、楽しいよ(初心者並感)。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0