dqwh1119 2016-08-08 13:02
浏览 32
已采纳

Golang接口{}类型的误解

I got a bug in Go when using an interface{} as function parameter type, when given a non-pointer type, and using json.Unmarshal with it.

Because a piece of code is worth a thousand words, here is an example:

package main

import (
    "encoding/json"
    "fmt"
)

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

type Test struct {
    Foo string
}

func main() {
    test(Test{})
}

Which outputs:

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

json.Unmarshal turns my struct to a map[string]interface{} oO...

Little readings later explains some of it, interface{} is a type in itself, not some sort of typeless container, which explains the *interface{}, and the fact that json.Unmarshal could not get the initial type, and returned a map[string]interface{}..

From Unmarshal docs:

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

And if I pass a pointer to the test function like so, it works:

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

func main() {
    test(&Test{})
}

Which outputs:

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

Cool, the data is unmarshalled and all, now in this second snippet I removed the & when calling Unmarshal. Because I have a *Test in i, no use for it.

So in all logic, if I put back the & to i when calling Unmarshal it should mess up with i's type again. But no.

If I run:

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

func main() {
    test(&Test{})
}

Well it still works:

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

And now I'm out of google search queries.

  • 写回答

1条回答 默认 最新

  • dovhpmnm31216 2016-08-08 13:34
    关注

    The right scenario

    interface{} is a wrapper for any value and of any type. An interface schematically wraps a (value; type) pair, a concrete value and its type. More details on this: The Laws of Reflection #The representation of an interface.

    json.Unmarshal() already takes the value of type interface{}:

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

    So if you already have an interface{} value (the i interface{} parameter of the test() function), don't try to take its address, just pass it along as-is.

    Also note that for any package to modify a value stored in an interface{}, you need to pass a pointer to it. So what should be in i is a pointer. So the right scenario is to pass *Test to test(), and inside test() pass i to json.Unmarshal() (without taking its address).

    Explanation of other scenarios

    When i contains *Test and you pass &i, it will work because the json package will simply dereference the *interface{} pointer, and finds an interface{} value, which wraps a *Test value. It's a pointer, so it's all good: unmarshals the JSON object into the pointed Test value.

    When i contains Test and you pass &i, same thing goes as above: *interface{} is dereferenced, so it finds an interface{} which contains a non-pointer: Test. Since the json package can't unmarshal into a non-pointer value, it has to create a new value. And since the passed value to the json.Unmarshal() function is of type *interface{}, it tells the json package to unmarshal the data into a value of type interface{}. This means the json package is free to choose which type to use. And by default the json package unmarshals JSON objects into map[string]interface{} values, so that is what's created and used (and eventually put into the value pointed by the pointer you passed: &i).

    All in all

    All in all, avoid using pointers to interfaces. Instead "put" pointers into the interfaces (the interface value should wrap the pointer). When you already have an interface{} holding a pointer, just pass it along.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥50 易语言把MYSQL数据库中的数据添加至组合框
  • ¥20 求数据集和代码#有偿答复
  • ¥15 关于下拉菜单选项关联的问题
  • ¥20 java-OJ-健康体检
  • ¥15 rs485的上拉下拉,不会对a-b<-200mv有影响吗,就是接受时,对判断逻辑0有影响吗
  • ¥15 使用phpstudy在云服务器上搭建个人网站
  • ¥15 应该如何判断含间隙的曲柄摇杆机构,轴与轴承是否发生了碰撞?
  • ¥15 vue3+express部署到nginx
  • ¥20 搭建pt1000三线制高精度测温电路
  • ¥15 使用Jdk8自带的算法,和Jdk11自带的加密结果会一样吗,不一样的话有什么解决方案,Jdk不能升级的情况