Golang interface{} 类型误解

Golang interface{} type misunderstanding

我在使用 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可以没有得到初始类型,返回了一个map[string]interface{}..

来自 Unmarshal 文档:

To unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value: [...]

如果我像这样传递一个指向测试函数的指针,它就会工作:

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 时删除了 &。因为我在i里有一个*Test,没用。

所以在所有逻辑中,如果我在调用 Unmarshal 时将 & 放回 i,它应该会再次弄乱 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 个搜索查询了。

正确的场景

interface{} 是任何值和任何类型的包装器。一个接口示意性地包装了一个 (value; type) 对,一个具体的值及其类型。有关此的更多详细信息:The Laws of Reflection #The representation of an interface.

json.Unmarshal() 已经采用类型 interface{}:

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

所以如果你已经有了一个 interface{} 值(test() 函数的 i interface{} 参数),不要试图获取它的地址,只需将它作为-是。

另请注意,对于任何修改存储在 interface{} 中的值的包,您需要传递一个指向它的指针。所以 i 中应该是一个指针。所以正确的场景是将 *Test 传递给 test(),并在 test() 内部将 i 传递给 json.Unmarshal()(不获取其地址)。

其他场景的解释

i 包含 *Test 并且您传递 &i 时,它将起作用,因为 json 包将简单地取消引用 *interface{} 指针,并找到一个 interface{} 值,它包含一个 *Test 值。它是一个指针,所以一切都很好:将 JSON 对象解组为指向的 Test 值。

i 包含 Test 并且你传递 &i 时,同样的事情发生在上面:*interface{} 被取消引用,所以它找到一个 interface{}包含一个非指针:Test。由于 json 包无法解组为非指针值,因此它必须创建一个新值。由于传递给 json.Unmarshal() 函数的值是 *interface{} 类型,它告诉 json 包将数据解组为 interface{} 类型的值。这意味着 json 包可以自由选择要使用的类型。默认情况下,json 包将 JSON 对象解组为 map[string]interface{} 值,因此这就是创建和使用的内容(并最终放入您传递的指针指向的值中:&i).

总而言之

总而言之,避免使用指向接口的指针。相反 "put" 指向接口的指针(接口值应该包含指针)。当你已经有一个 interface{} 持有一个指针时,只需传递它。