Указатель на интерфейс против интерфейса, содержащего указатель при демаршалинге JSON - PullRequest
0 голосов
/ 28 мая 2018

Рассмотрим следующие определения структуры и интерфейса.

type Foo interface {
    Operate()
}

type Bar struct {
    A int
}

func (b Bar) Operate() {
    //...
}

Теперь, если мы попытаемся выполнить следующее ( детская площадка ):

var x Foo = Bar{}
err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
fmt.Printf("x: %+v\nerr: %s\n", x, err) 

мы получимследующий вывод:

x: {A:0}
err: json: cannot unmarshal object into Go value of type main.Foo

Однако, заменяя базовые данные типом структуры, он работает без помех ( детская площадка ):

var x Foo = &Bar{}
err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
fmt.Printf("x: %+v\nerr: %s\n", x, err)

Вывод:

x: &{A:5}    
err: %!s(<nil>)

Однако, это меня смущает.В нашем обращении к Unmarshall мы по-прежнему передаем указатель на x, который, насколько я понимаю, должен быть достаточным, чтобы позволить нам модифицировать Bar под ним.В конце концов, указатели - это просто адреса в памяти.Если мы передадим этот адрес, мы сможем изменить его, нет?Почему работает второй пример, а не первый?Почему структура, являющаяся указателем, имеет все значение?

1 Ответ

0 голосов
/ 28 мая 2018

Корень различного поведения заключается в том, что если пакет json должен создать новое значение, это значение должно быть конкретным типом и не может быть (не конкретным) типом интерфейса.

Давайте уточним это.

Сначала давайте рассмотрим ваш второй, рабочий пример.Ваша переменная x типа Foo содержит значение указателя типа *Bar.Когда вы передаете &x в json.Unmarshal(), пакет json получит значение указателя *Foo.Указатель на интерфейс!Не следует использовать, но это так.Пакет json разыменует указатель и получает упакованное значение типа *Bar.Поскольку это не nil указатель, пакет json может - и будет - использовать его для демаршалинга.Все хорошо!Пакет json изменит значение острое .

Что происходит в вашем первом примере?

В вашем первом примере переменная x типа Fooоборачивает не указатель структурное значение. Значения в интерфейсе не могут быть изменены.

Что это значит?Пакет json снова получит значение типа *Foo.Затем он идет дальше и разыменовывает его, и получает значение Bar, заключенное в интерфейс.Значение Bar внутри интерфейса Foo не может быть изменено.Единственный способ для json пакета «доставить» результаты - создать новое значение, реализующее Foo, и сохранить это значение там, где изначально передано значение *Foo.Но значения Foo не могут быть созданы, это не конкретный тип интерфейса.Так что unmarshal возвращается с ошибкой.

Некоторое дополнение.Ваш второй рабочий пример:

var x Foo = &Bar{}
err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
fmt.Printf("x: %+v\nerr: %s\n", x, err)

Это работает, потому что мы "подготавливаем" не nil *Bar значение для разархивирования, поэтому пакету json не нужно создавать значениесам.Мы уже подготовили и передали значение для типа интерфейса Foo (являющегося значением конкретного типа *Bar).

Если бы мы сохранили «типизированный» nil, указанный в x, как это:

var x Foo = &Bar{}
x = (*Bar)(nil)
err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
fmt.Printf("x: %+v\nerr: %s\n", x, err)

Мы получим ту же ошибку (попробуйте на Go Playground ):

x: <nil>
err: json: cannot unmarshal object into Go value of type main.Foo

Объяснение то же самое: json пакет не может использовать указатель nil, чтобы демонтировать значение в.Снова нужно будет создать значение неконкретного типа Foo, но это невозможно.Могут быть созданы только значения конкретных типов.

...