如何使用反射提取类型参数

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

How to extract type parameters using reflection

问题

上下文:我正在编写一个通用的自动映射器,它接受两种类型的结构体,检查每个结构体的字段是否具有给定的标签,然后假设它们具有匹配的标签和类型,将源结构体的值复制到目标结构体中。每当一个结构体字段是另一个(嵌套的)结构体时,我希望自动映射函数能够进行递归调用,一直映射到底部。

问题:我只能传递根结构体的具体类型。一旦我进入使用反射的通用函数,似乎无法提取嵌套结构体的类型。
虽然我可以将Value.Interface()作为参数传递,但我仍然需要传递类型参数。

这是一些简化的代码来展示问题。

type Alpha struct {
	Nested Beta `automap:"nested"`
}

type Beta struct {
	Info string `automap:"info"`
}

type Foo struct {
	Nested Bar `automap:"nested"`
}

type Bar struct {
	Info string `automap:"info"`
}

func TestAutoMap(t *testing.T) {

	b := Beta{Info: "Hello from Beta!"}
	a := Alpha{Nested: b}

	f, err := AutoMap[Alpha, Foo](a)
	if err != nil {
		fmt.Println(err)
		t.Fail()
	}
	fmt.Println("f.nested.info:", f.Nested.Info)
}

func AutoMap[S, T any](source S) (target T, err error) {

	targetStruct := reflect.ValueOf(&target).Elem()
	sourceStruct := reflect.ValueOf(&source).Elem()

	// .Type and .Kind directly did not work.
	nestedSourceType := ??? // I want this to be type Beta.
	nestedTargetType := ??? // I want this to be type Bar.

	sourceInterface := sourceStruct.Interface()

	t, err := AutoMap[nestedSourceType, nestedTargetType](sourceInterface)
	if err != nil {
		return target, err
	}
	target = t

	return target, nil
}
英文:

Context: I'm writing a generic auto-mapper that takes two types of structs, checks each field of said structs for a given tag, then copies the value from the source struct to the target struct assuming that they have matching tags and types. Whenever a struct field is another (nested) struct, I want the auto-mapper function to do a recursive call, auto-mapping all the way down the rabbit hole.

Problem: I'm only able to pass the concrete type of the root structs. Once I'm inside the generic function that's using reflection, trying to extract the nested struct types doesn't seem possible.
While I can pass the Value.Interface() as an argument, I still need to pass the type parameters as well.

Here is some simplified code to show the problem.

type Alpha struct {
	Nested Beta `automap:"nested"`
}

type Beta struct {
	Info string `automap:"info"`
}

type Foo struct {
	Nested Bar `automap:"nested"`
}

type Bar struct {
	Info string `automap:"info"`
}

func TestAutoMap(t *testing.T) {

	b := Beta{Info: "Hello from Beta!"}
	a := Alpha{Nested: b}

	f, err := AutoMap[Alpha, Foo](a)
	if err != nil {
		fmt.Println(err)
		t.Fail()
	}
	fmt.Println("f.nested.info:", f.Nested.Info)
}

func AutoMap[S, T any](source S) (target T, err error) {

	targetStruct := reflect.ValueOf(&target).Elem()
	sourceStruct := reflect.ValueOf(&source).Elem()

	// .Type and .Kind directly did not work.
	nestedSourceType := ??? // I want this to be type Beta.
	nestedTargetType := ??? // I want this to be type Bar.

	sourceInterface := sourceStruct.Interface()

	t, err := AutoMap[nestedSourceType, nestedTargetType](sourceInterface)
	if err != nil {
		return target, err
	}
	target = t

	return target, nil
}

答案1

得分: -1

根据@mkopriva的建议,我想分享一下我遇到的问题的简单解决方案。

请随意进行更正或改进,但请记住,我故意没有在下面包含各种检查和断言。

Go Playground 示例

type Alpha struct {
	NestedOnce Beta
}

type Beta struct {
	NestedTwice Gamma
}

type Gamma struct {
	Info string
}

type Foo struct {
	NestedOnce Bar
}

type Bar struct {
	NestedTwice Baz
}

type Baz struct {
	Info string
}

func TestAutoMap(t *testing.T) {

	g := Gamma{"Hello from Gamma!"}
	b := Beta{g}
	a := Alpha{b}

	f, err := AutoMap[Foo](a)
	if err != nil {
		fmt.Println(err)
		t.Fail()
	} else {
		fmt.Println("Foo.NestedOnce.NestedTwice.Info:", f.NestedOnce.NestedTwice.Info)
	}
}

func AutoMap[T any](source any) (target T, err error) {

	// 从函数参数类型中去掉 'any'。
	sourceStruct := reflect.ValueOf(&source).Elem().Elem()

	targetStruct := reflect.ValueOf(&target).Elem()

	err = autoMap(sourceStruct, targetStruct)

	return target, err
}

func autoMap(s, t reflect.Value) error {

	sourceField := s.Field(0)
	targetField := t.Field(0)

	if sourceField.Kind() == reflect.Struct {
		err := autoMap(sourceField, targetField)
		if err != nil {
			return err
		}
		return nil
	}

	targetField.Set(sourceField)

	return nil
}

英文:

Having followed the advice of @mkopriva, I want to share a simple solution to the problem I had.

Please feel free to correct or improve it, but keep in mind that I am deliberately not including all sorts of checks and assertions below.

(Go Playground Example)

type Alpha struct {
NestedOnce Beta
}
type Beta struct {
NestedTwice Gamma
}
type Gamma struct {
Info string
}
type Foo struct {
NestedOnce Bar
}
type Bar struct {
NestedTwice Baz
}
type Baz struct {
Info string
}
func TestAutoMap(t *testing.T) {
g := Gamma{"Hello from Gamma!"}
b := Beta{g}
a := Alpha{b}
f, err := AutoMap[Foo](a)
if err != nil {
fmt.Println(err)
t.Fail()
} else {
fmt.Println("Foo.NestedOnce.NestedTwice.Info:", f.NestedOnce.NestedTwice.Info)
}
}
func AutoMap[T any](source any) (target T, err error) {
// Peel off 'any' from the function parameter type.
sourceStruct := reflect.ValueOf(&source).Elem().Elem()
targetStruct := reflect.ValueOf(&target).Elem()
err = autoMap(sourceStruct, targetStruct)
return target, err
}
func autoMap(s, t reflect.Value) error {
sourceField := s.Field(0)
targetField := t.Field(0)
if sourceField.Kind() == reflect.Struct {
err := autoMap(sourceField, targetField)
if err != nil {
return err
}
return nil
}
targetField.Set(sourceField)
return nil
}

huangapple
  • 本文由 发表于 2023年4月6日 17:46:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/75948086.html
匿名

发表评论

匿名网友

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

确定