Golang interface{} type misunderstanding

江枫思渺然 提交于 2020-01-13 13:00:12

问题


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\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{})
}

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\n", i)
    fmt.Printf("%T\n", &i)
    json.Unmarshal(j, i)
    fmt.Printf("%T\n", 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\n", i)
    fmt.Printf("%T\n", &i)
    json.Unmarshal(j, &i)
    fmt.Printf("%T\n", 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:


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.



来源:https://stackoverflow.com/questions/38829941/golang-interface-type-misunderstanding

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!