Unmarshal встроенный указатель поля паникует, когда поле реализует UnmarshalJSON - PullRequest
0 голосов
/ 31 декабря 2018

У меня есть struct, в который встроен указатель на другой struct.Когда я использую поведение по умолчанию json.Unmarshal, оно работает отлично.Но когда я реализую UnmarshalJSON для встроенного struct типа, но не внешнего struct, тогда паникую с разыменованием нулевого указателя.

Если я реализую UnmarshalJSON для внешнего типа struct, то это сработает.Однако внешняя структура имеет много полей, которые я бы предпочел не разбирать вручную.

  1. Почему реализация UnmarshalJSON на одном, а не на другом вызывает панику?
  2. Есть ли способ заставить его работать без реализованного UnmarshalJSON для внешнего типа?
  3. Если нет, есть ли более простой / менее ручной способ реализации UnmarshalJSON для внешнего типа?

Примечание: Есть вопрос с похожим названием: " json.Unmarshal не работает, если во встроенном типе есть UnmarshalJSON ", но проблемаэто отличается от моего.

tl; dr: Остальная часть этого вопроса является лишь длинным примером вышеупомянутого.

Базовый пример

(игра.golang.org версия примера)

Две структуры, одна со встроенным указателем поля на другую:

(Упрощенно, например, это недействительно не нужен свой UnmarshalJSON, но это демонстрирует проблему.)

type Obj struct {
    X int `json:"x"`
}

type Container struct {
    *Obj
    Y int `json:"y"`
}

Вызов unmarshal:

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
[...]

Вопрос: Почему это паникует здесь, но не из-за поведения по умолчанию unmarshal?Я думаю, что если здесь передается 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 голосов
/ 31 декабря 2018

Поскольку поведение по умолчанию проверяет nil, а пользовательский unmarshaller - нет.Вам нужна некоторая логика в вашем 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...