如何在 interface{} 上使用 reflect.NewAt 函数?

huangapple go评论78阅读模式
英文:

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) // t1的类型变为map[string]interface{},而不是struct Stu

	newPointer := reflect.New(reflect.ValueOf(t2).Type()).Interface()
	json.Unmarshal(jsonData, newPointer)
	fmt.Println(newPointer) // 这样可以工作,但需要分配内存来保存临时的新变量

	brokenPointer := reflect.NewAt(reflect.ValueOf(t3).Type(), unsafe.Pointer(&t3)).Interface()
	json.Unmarshal(jsonData, brokenPointer)
	fmt.Println(brokenPointer) // 我想根据已存在的变量t3获取原始类型的指针,但会导致崩溃。

}

如果在编码时不知道interface{}的具体类型就无法使用interface.(Type)进行类型转换
那么如何在interface{}上使用reflect.NewAt呢

如果有一个包含返回interface{}类型的方法的接口其具体类型为struct但在编码时无法确定struct的类型我需要使用json.Unmarshal解码由json编码的数据我不想得到map[string]interface{}所以我需要将接收者的类型设置为interface{}的具体类型的指针使用reflect.New很容易但会消耗额外的内存所以我想知道如何在现有的interface{}上使用reflect.NewAt
英文:
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{}.

答案1

得分: 1

我想解释一下问题是什么。

函数MakeStu()返回的是空接口(empty interface),我们无法控制它的具体类型。这会使具体类型对json.Unmarshal()来说是“不可见的”,json.Unmarshal()将其视为空接口,而不是具体类型Stu。我们需要以某种方式将具体类型传递给解码器。

我会使用类型断言来解决这个问题:

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

	switch c := t1.(type) {
	default:
		_ = json.Unmarshal(jsonData, &c)
		t1 = c
	}

	fmt.Println(t1)
}

类型断言将空接口转换为具体类型,json.Unmarshal()将按照具体类型进行处理。可能仍然会有额外的分配,但代码更易读,并且不依赖反射。

如果我感到非常冒险,我会添加一个接受空接口指针的辅助函数,像这样:

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

	return err
}

这样就可以像这样使用:

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

我觉得这看起来很简洁,不涉及反射,但它没有使用你自己建议的reflect.NewAt()。希望你仍然觉得我的建议有用。

英文:

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.

huangapple
  • 本文由 发表于 2023年6月18日 22:26:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76501029.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定