Корень различного поведения заключается в том, что если пакет 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
, но это невозможно.Могут быть созданы только значения конкретных типов.