Go言語で埋め込みをしたstructでのjson.UnmarshalJSON
goを書いていて、部分実装やゆるふわなJSONを扱うためにstructの埋め込みを使うことがある。 そのstructに対してJSONを読み込む時に少しハマった事があるのでメモ。
まずはじめに、こんな具合でJSNONをOuter
にマッピングしていた。
https://play.golang.org/p/zGo779zhxI
package main import ( "encoding/json" "fmt" ) type Outer struct { InnerA InnerB } type InnerA struct { FieldA string `json:"a_field"` } type InnerB struct { FieldB string `json:"b_field"` } func main() { var v Outer data := []byte(`{"a_field": "A", "b_field": "B"}`) if err := json.Unmarshal(data, &v); err != nil { fmt.Println("[Error] " + err.Error()) return } fmt.Printf("%+v", v) // {InnerA:{FieldA:A} InnerB:{FieldB:B}} }
開発中にJSONのb_field
が文字でないfalseを返す場合があることが分かってしまったので以下の様に対応した。
https://play.golang.org/p/0WszSKVN0m
package main import ( "encoding/json" "fmt" ) type Outer struct { InnerA InnerB } type InnerA struct { FieldA string `json:"a_field"` } type InnerB struct { FieldB string `json:"b_field"` } func (e *InnerB) UnmarshalJSON(data []byte) error { var v struct { FieldB interface{} `json:"b_field"` } if err := json.Unmarshal(data, &v); err != nil { return nil } switch v.FieldB.(type) { case string: break default: return nil } e.FieldB = v.FieldB.(string) return nil } func main() { var v Outer data := []byte(`{"a_field": "A", "b_field": "B"}`) if err := json.Unmarshal(data, &v); err != nil { fmt.Println("[Error] " + err.Error()) return } fmt.Printf("%+v", v) // => {InnerA:{FieldA:} InnerB:{FieldB:B}} }
これで上手くいくと思いきや、InnerA
のフィールドがゼロ値になってしまっている。
そこで、面倒だけどOuter
でそれぞれUnmarshalすることでなんとかなった。
https://play.golang.org/p/kii9ZmoX1O
package main import ( "encoding/json" "fmt" ) type Outer struct { InnerA InnerB } type InnerA struct { FieldA string `json:"a_field"` } type InnerB struct { FieldB string `json:"b_field"` } func (e *Outer) UnmarshalJSON(data []byte) error { var err error err = json.Unmarshal(data, &e.InnerA) if err != nil { return err } err = json.Unmarshal(data, &e.InnerB) if err != nil { return err } return nil } func (e *InnerB) UnmarshalJSON(data []byte) error { var v struct { FieldB interface{} `json:"b_field"` } if err := json.Unmarshal(data, &v); err != nil { return nil } switch v.FieldB.(type) { case string: break default: return nil } e.FieldB = v.FieldB.(string) return nil } func main() { var v Outer data := []byte(`{"a_field": "A", "b_field": "B"}`) if err := json.Unmarshal(data, &v); err != nil { fmt.Println("[Error] " + err.Error()) return } fmt.Printf("%+v", v) }
理由は後で調べる。