问题
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