TL;DR
import "encoding/json"
type User struct {
Id int64
Name string
}
func (u User) MarshalJSON() ([]byte, error) {
type __user User
return json.Marshal(__user(u))
}
MarshalJSON()
を持った状態だと無限ループするので、別で宣言し変換してから json.Marshal()
に渡す
はじめに
本稿は Go 1.12 で動作確認した内容になります
なんで発生したの?
import "encoding/json"
type User struct {
Id int64
Name string
}
func (u User) MarshalJSON() ([]byte, error) {
// 処理
return json.Marshal(u)
}
とある処理を加えてから JSON として出力しようとしたら無限ループに…
どういう条件でループに入るか試してみた
import "encoding/json"
type User struct {
Id int64
Name string
}
// パターン1: 発生しない
func (u User) MarshalJSON() ([]byte, error) {
return json.Marshal("string")
}
// パターン2: 発生しない
func (u User) MarshalJSON() ([]byte, error) {
return json.Marshal(struct{ Test string }{Test: "dummy"})
}
// パターン3: 発生する
func (u User) MarshalJSON() ([]byte, error) {
return json.Marshal(u)
}
最初は json.Marshal()
に構造体を渡すのがダメかと思ったんですが、そうじゃないのがパターン 2 でわかった
encoding/json のソースコード読んでみた
https://github.com/golang/go/blob/release-branch.go1.12/src/encoding/json/encode.go#L444
そもそも MarshalJSON()
がどこで呼ばれてるのか調べてみると marshalerEncoder
/ addrMarshalerEncoder
のどちらかみたい
Marshal() (encoding/json.go)
-> MarshalJSON() (書き手のソースコード)
-> Marshal()
-> MarshalJSON()
...
といった流れになってそう
https://github.com/golang/go/blob/release-branch.go1.12/src/encoding/json/encode.go#L157
実際の処理を追ってみる
json.Marshal()
の処理はここから始まって
https://github.com/golang/go/blob/release-branch.go1.12/src/encoding/json/encode.go#L334
ここで型別に処理が変わってる
https://github.com/golang/go/blob/release-branch.go1.12/src/encoding/json/encode.go#L392
newTypeEncoder()
内の if t.Implements(marshalerType) {}
あたりで引っかかって marshalerEncoder
の処理に入ってそう
試してみた
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type TestA struct {
Test string
}
type TestB struct {
Test string
}
func (t TestB) MarshalJSON() ([]byte, error) {
return json.Marshal(t)
}
type TestBdash TestB
func main() {
a := TestA{"dummy"}
b := TestB{"dummy"}
marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem()
// false
fmt.Println(reflect.ValueOf(a).Type().Implements(marshalerType))
// true
fmt.Println(reflect.ValueOf(b).Type().Implements(marshalerType))
bdash := TestBdash(b)
// false
fmt.Println(reflect.ValueOf(bdash).Type().Implements(marshalerType))
}
やっぱり MarshalJSON()
を持つ構造体だと if t.Implements(marshalerType) {}
で true
になる
そこで最初に書いた結論に至ったという感じ
おわりに
Go 歴短かいけど、読みやすいおかげでバグの当たりをつけやすかった