1
0

More than 5 years have passed since last update.

MarshalJSON で無限ループが発生した時に調べた内容と対処方法

Last updated at Posted at 2019-06-15

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))
}

playground

やっぱり MarshalJSON() を持つ構造体だと if t.Implements(marshalerType) {}true になる
そこで最初に書いた結論に至ったという感じ

おわりに

Go 歴短かいけど、読みやすいおかげでバグの当たりをつけやすかった

1
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
1
0