我有一个struct
,它嵌入了指向另一个struct
的嵌入式指针。当我使用默认的json.Unmarshal
行为时,它可以完美运行。但是,当我为嵌入式 UnmarshalJSON
的类型而不是外部struct
实现struct
时,则使用空指针取消引用进行恐慌。
如果我也为外部UnmarshalJSON
类型实现了struct
,那么它将起作用。但是,外部结构有很多字段,我不希望手动解组。
UnmarshalJSON
会引起恐慌? UnmarshalJSON
的情况下使其工作? UnmarshalJSON
? 注意:有一个标题相似的问题“json.Unmarshal fails when embedded type has UnmarshalJSON”,但该问题与我的不同。
tl; dr:这个问题的其余部分只是上面冗长的示例。
基本示例
(例如play.golang.org version)
这两个结构,一个具有指向另一个的嵌入式字段指针:
(例如,经过简化-确实不需要它自己的
UnmarshalJSON
,但是它说明了问题。)type Obj struct {
X int `json:"x"`
}
type Container struct {
*Obj
Y int `json:"y"`
}
调用元帅:
func main() {
b := []byte(`{"x": 5, "y": 3}`)
c := &Container{}
err := json.Unmarshal(b, c)
if err != nil {
fmt.Printf("error ummarshalling json: %+v\n", err)
return
}
fmt.Printf("unmarshalled: %+v --> %+v\n", c, c.Obj)
}
如果不实现任何
UnmarshalJSON
函数,则可以正常工作:unmarshalled: &{Obj:0x416080 Y:3} --> &{X:5}
恐慌
但是,如果仅将
UnmarshalJSON
添加到嵌入式Obj
类型,则程序恐慌,因为json.Unmarshal
调用在尝试解组nil
时会传递*Obj
指针。func (o *Obj) UnmarshalJSON(b []byte) (err error) {
m := make(map[string]int)
err = json.Unmarshal(b, &m)
if err != nil {
return nil
}
o.X = m["x"] // the line indicated by panic
return nil
}
输出:
panic: runtime error: invalid memory address or nil pointer dereference
[...]
main.(*Obj).UnmarshalJSON(0x0, 0x416030, 0x10, 0x10, 0x0, 0x0)
/tmp/sandbox185809294/main.go:18 +0x130
[...]
问题:为什么在这里会出现恐慌,但默认的解组行为却不会呢?我认为如果在这里传递
nil
*Obj
,那么默认行为也会绕过nil
指针...解决恐慌
当我为外部
UnmarshalJSON
类型实现Container
时,它不再感到恐慌:func (c *Container) UnmarshalJSON(b []byte) (err error) {
m := make(map[string]int)
err = json.Unmarshal(b, &m)
if err != nil {
return err
}
c.Obj = &Obj{X: m["x"]}
c.Y = m["y"]
return nil
}
但是,如果实际
Container
和实际Container
都具有比此更多的字段,并且每种字段具有不同的类型,则以这种方式手动解组Obj
将变得乏味。问题:有没有更简单的方法来防止这种恐慌?
最佳答案
因为默认行为会检查nil
,而您的自定义解组器则不会。您需要在UnmarshalJSON
中添加一些逻辑来检查o
是否为nil
并正常运行,而不是假设o
不是nil
(通过尝试访问其字段之一),从而引发恐慌。
func (o *Obj) UnmarshalJSON(b []byte) (err error) {
if o == nil {
return nil // maybe? What do you want to happen in this case?
}
m := make(map[string]int)
err = json.Unmarshal(b, &m)
if err != nil {
return nil
}
o.X = m["x"] // the line indicated by panic
return nil
}
同样仅供将来参考,您的
*Obj
字段不是“匿名字段”,而是嵌入式字段:https://golang.org/ref/spec#Struct_types