简体   繁体   中英

Golang interface{} type misunderstanding

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM