Почему json.Unmarshal работает со ссылкой, но не с указателем?

Этот пример из json.Unmarshal docs (слегка модифицированный для простоты использования Animal вместо []Animal) работает без ошибок:

Ссылка на рабочий стол на рабочем месте

// ...
var animals Animal
err := json.Unmarshal(jsonBlob, &animals)
// ...

Но этот слегка измененный пример не делает:

ссылка на игровое поле нерабочего примера

// ...
var animals *Animal
err := json.Unmarshal(jsonBlob, animals)
// ...

Он отображает эту неясную ошибку, которая действительно не помогает (выглядит больше как вызов функции, чем ошибка IMO):

json: Unmarshal (nil * main.Animal)

Это похоже на то, что animals - неинициализированный указатель. Но документы говорят (акцент мой):

Unmarshal unmarshals JSON в значение, на которое указывает указатель. Если указатель равен нулю, Unmarshal выделяет новое значение для его указания.

Итак, почему во всплывающем примере неудачное отключение маршала и показывает ту непонятную ошибку?

(Кроме того, это "unmarshalling" или "unmarshaling" (один L)? Документы используют оба.)

Ответ 1

Вы столкнулись с InvalidUnmarshalError (см. строки 109 и 110 в decode.go).

//InvalidUnmarshalError описывает недопустимый аргумент, переданный Unmarshal.
// (Аргумент Unmarshal должен быть указателем не-nil.)

Кажется, что документы могли бы сделать с некоторыми пояснениями, поскольку приведенная выше цитата и комментарий ниже из источника Unmarshal, похоже, противоречат друг другу.

Если указатель равен нулю, Unmarshal выделяет новое значение для него.

Ответ 2

Потому что ваш указатель равен нулю.

Если вы его инициализируете, он работает: http://play.golang.org/p/zprmV0O1fG

var animals *Animal = &Animal{}

Кроме того, это может быть записано в любом случае (согласованность в одном документе была бы приятной): http://en.wikipedia.org/wiki/Marshalling_(computer_science)

Ответ 3

Я считаю, что проблема заключается в том, что, хотя вы можете передать указатель на nil на Unmarshal(), вы не можете передать значение указателя nil.

Указатель на nil будет выглядеть так:

var v interface{}
json.Unmarshal(text, &v)

Значение v равно nil, но указатель на v является ненулевым адресом указателя. Это ненулевой указатель, который указывает на интерфейс nil {} (который сам по себе является типом указателя). Unmarshal не возвращает ошибку в этом случае.

Указатель nil будет выглядеть так:

var v *interface{}
json.Unmarshal(text, v)

В этом случае тип v равен pointer to an interface{}, но как и при любом объявлении var в golang, начальное значение v является нулевым значением типа. Таким образом, v - это указатель нулевого значения, что означает, что он не указывает на какое-либо допустимое место в памяти.

Как упоминалось в fooobar.com/questions/353411/..., json.Unmarshal() требует действительного указателя на что-то, поэтому он может что-то изменить (будь то структура с нулевым значением, или указатель).

Ответ 4

У меня было подобное состояние раньше, но в другом случае. Это связано с концепцией интерфейса в Go. Если функция объявляет интерфейс как аргумент или возвращаемое значение, вызывающий должен передать или вернуть ссылку

В вашем случае json.Unmarshal принять интерфейс в качестве второго аргумента