Интерфейс Golang {} вызывает непонимание

У меня есть ошибка в Go при использовании типа параметра interface{} как функция, когда задан тип не указателя, и с помощью json.Unmarshal с ним.

Поскольку кусок кода стоит тысячи слов, вот пример:

package main
import (
 "encoding/json"
 "fmt"
)
func test(i interface{}) {
 j := []byte(`{ "foo": "bar" }`)
 fmt.Printf("%T\n", i)
 fmt.Printf("%T\n", &i)
 json.Unmarshal(j, &i)
 fmt.Printf("%T\n", i)
}
type Test struct {
 Foo string
}
func main() {
 test(Test{})
}

Какие выходы:

main.Test
*interface {}
map[string]interface {}

json.Unmarshal превращает мою структуру в map[string]interface{} oO...

Небольшие чтения позже объясняют некоторые из них, interface{} - это сам по себе тип, а не какой-то нетрадиционный контейнер, который объясняет *interface{}, и тот факт, что json.Unmarshal не смог получить начальный тип и возвратил a map[string]interface{}..

Из Unmarshal docs:

Чтобы размонтировать JSON в значение интерфейса, Unmarshal сохраняет один из они в значении интерфейса: [...]

И если я передаю указатель на тестовую функцию так, она работает:

func test(i interface{}) {
 j := []byte(`{ "foo": "bar" }`)
 fmt.Printf("%T\n", i)
 fmt.Printf("%T\n", &i)
 json.Unmarshal(j, i)
 fmt.Printf("%T\n", i)
 fmt.Println(i)
}
func main() {
 test(&Test{})
}

Какие выходы:

*main.Test
*interface {}
*main.Test
&{bar}

Прохладный, данные немаршарируются и все, теперь во втором фрагменте я удалил & при вызове Unmarshal. Поскольку у меня есть *Test в i, не нужно использовать его.

Итак, во всей логике, если я вернул & в i при вызове Unmarshal, он снова испортится с типом i. Но нет.

Если я запустил:

func test(i interface{}) {
 j := []byte(`{ "foo": "bar" }`)
 fmt.Printf("%T\n", i)
 fmt.Printf("%T\n", &i)
 json.Unmarshal(j, &i)
 fmt.Printf("%T\n", i)
 fmt.Println(i)
}
func main() {
 test(&Test{})
}

Ну, он все еще работает:

*main.Test
*interface {}
*main.Test
&{bar}

И теперь я не в поисковых запросах Google.

1 ответ

Правильный сценарий

interface{} - это оболочка для любого значения и любого типа. Интерфейс схематически обертывает пару (value; type), конкретное значение и его тип. Подробнее об этом: Законы отражения # Представление интерфейса.

json.Unmarshal() уже принимает значение типа interface{}:

func Unmarshal(data []byte, v interface{}) error

Итак, если у вас уже есть interface{} значение (i interface{} параметр test() функции), не пытайтесь взять его адрес, просто передать его как есть.

Также обратите внимание, что для любого пакета для изменения значения, хранящегося в interface{}, вам нужно передать указатель на него. Итак, что должно быть в i является указателем. Поэтому правильный сценарий, чтобы передать *Test в test(), внутри test() передать i в json.Unmarshal() (без учета его адрес).

Объяснение других сценариев

Когда i содержит *Test и вы передадите &i, он будет работать, потому что json пакет будет просто разыменовать *interface{} указатель, и находит interface{} значение, которое оборачивает в *Test стоимость. Это указатель, так что все это хорошо: unmarshals объект JSON в указанное значение Test.

Когда i содержит Test и проходите &i, то же самое происходит, как описано выше: *interface{} разыменовывается, поэтому он находит interface{}, который содержит не-указатель: Test. Поскольку пакет json не может отменить маркер в значение, отличное от указателя, он должен создать новое значение. А поскольку переданное значение функции json.Unmarshal() типа *interface{}, она говорит json пакет распаковать данные в значение типа interface{}. Это означает, что пакет json может выбирать, какой тип использовать. И по умолчанию json пакет демаршализует JSON объектов в map[string]interface{} значений, так это то, что создается и используется (и в конечном итоге положить в значение указываемой указателю, переданному: &i).

Всего

В общем, избегайте использования указателей на интерфейсы. Вместо этого "поместите" указатели в интерфейсы (значение интерфейса должно обернуть указатель). Когда у вас уже есть interface{}, удерживающий указатель, просто передайте его.

licensed under cc by-sa 3.0 with attribution.