У меня есть struct
, в который встроен указатель на другой struct
.Когда я использую поведение по умолчанию json.Unmarshal
, оно работает отлично.Но когда я реализую UnmarshalJSON
для встроенного struct
типа, но не внешнего struct
, тогда паникую с разыменованием нулевого указателя.
Если я реализую UnmarshalJSON
для внешнего типа struct
, то это сработает.Однако внешняя структура имеет много полей, которые я бы предпочел не разбирать вручную.
- Почему реализация
UnmarshalJSON
на одном, а не на другом вызывает панику? - Есть ли способ заставить его работать без реализованного
UnmarshalJSON
для внешнего типа? - Если нет, есть ли более простой / менее ручной способ реализации
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
есть больше полейкроме этого, каждый с разными типами.
Вопрос: Есть ли более простой способ предотвратить эту панику?