Search code examples
goreflectionunsafe

How to use reflect.NewAt on interface{}?


package main

import (
    "encoding/json"
    "fmt"
    "reflect"
    "unsafe"
)

type Stu struct {
    Name string `json:"name"`
}

func MakeStu() interface{} {
    return Stu{
        Name: "Test",
    }
}

func main() {
    jsonData := []byte(`{"name":"New"}`)
    t1 := MakeStu()
    t2 := MakeStu()
    t3 := MakeStu()

    json.Unmarshal(jsonData, &t1)
    fmt.Println(t1) //Type of t1 becomes map[string]interface{} instead of struct Stu

    newPointer := reflect.New(reflect.ValueOf(t2).Type()).Interface()
    json.Unmarshal(jsonData, newPointer)
    fmt.Println(newPointer) //It works,but it need allocate memory to hold temp new variable

    brokenPointer := reflect.NewAt(reflect.ValueOf(t3).Type(), unsafe.Pointer(&t3)).Interface()
    json.Unmarshal(jsonData, brokenPointer)
    fmt.Println(brokenPointer) // I want to get the pointer of original type based on the existed variable t3,but it crashes.
}

If I don't know the concrete type of the interface{} when coding,I can't use interface.(Type) to cast. so how to use reflect.NewAt on interface{}?

If there is an interface that contains a method who returns interface{} whose concrete type is struct, but I can't determine the type of struct when coding. I need use json.Unmarshal to decode data that encoded by json. I don't want to get map[string]interface{} , so I need make the receiver's type that is the pointer of concrete type of the interface{} . Using reflect.New is easy but costing extra memory, so I am curious about how to use reflect.NewAt with existing interface{}.


Solution

  • I would like to spend a few sentences explaining what the problem is.

    The function MakeStu(), which we have no control over, returns the empty interface - not a concrete type. This will make the concrete type "invisible" to json.Unmarshal(), and json.Unmarshal() treats it as the empty interface, not as the concrete type Stu. Somehow we must convey the concrete type to the unmarshaller.

    I would solve this problem with a type switch:

    func main() {
        jsonData := []byte(`{"name":"New"}`)
        t1 := MakeStu()
    
        switch c := t1.(type) {
        default:
            _ = json.Unmarshal(jsonData, &c)
            t1 = c
        }
    
        fmt.Println(t1)
    }
    

    The type switch turns the empty interface into a concrete type, and json.Unmarshal() will treat it as such. You will probably still have the extra allocation, but the code is more readable, and you're not depending on reflection.

    If I was feeling really adventurous, I would add a helper function accepting a pointer to an empty interface like this:

    func unmarshal(data []byte, i *interface{}) (err error) {
        switch c := (*i).(type) {
        default:
            err = json.Unmarshal(data, &c)
            *i = c
        }
    
        return err
    }
    

    That would enable usage like this:

        jsonData := []byte(`{"name":"New"}`)
        t1 := MakeStu()
        unmarshal(jsonData, &t1)
    

    This looks clean to me and doesn't involve reflection, but it doesn't use reflect.NewAt() as you suggested yourself. I hope you find my suggestions useful none the less.