当字段实现UnmarshalJSON

时间:2018-12-31 17:04:07

标签: go

我有一个struct,它嵌入了指向另一个struct的嵌入式指针。当我使用默认的json.Unmarshal行为时,它可以完美运行。但是,当我为 embedded 的类型实现UnmarshalJSON而不是外部struct的类型struct时, ,然后使用空指针取消引用进行恐慌。

如果我也为外部UnmarshalJSON类型实现了struct,那么它将起作用。但是,外部结构有很多字段,我不希望手动解组。

  1. 为什么在一个而不是另一个上实施UnmarshalJSON会引起恐慌?
  2. 在没有为外部类型实现UnmarshalJSON的情况下,有没有办法使它工作?
  3. 如果不是,是否有更简单/更少的手动方式为外部类型实现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变得很乏味。

问题:有没有更简单的方法来防止这种恐慌?

1 个答案:

答案 0 :(得分:1)

因为默认行为会检查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字段不是“匿名字段”,而是 embedded 字段:https://golang.org/ref/spec#Struct_types