如何在函数内部使用在运行时定义的类型更新接口的值?

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

How to update the values of an interface within a function with the type defined at runtime?

问题

假设我有一个结构体 User:

type User struct {
    Name string `owm:"newNameFromAPI"`
}

下面的代码初始化了该结构体并将其传递给一个函数:

func main() {
    dest := User{
        Name: "Sebastien",
    }
    
    deepUpdate(dest, "owm")
}

该函数使用反射来遍历结构体的所有字段并相应地更新它们(为了清晰起见,删除了一些代码块):

func deepUpdate(destinationInterface interface{}, selector string) {

    // 这里我不应该知道类型是 `User`
    // 如果我只是使用 dest := destinationInterface,值将不会更新
    dest := destinationInterface.(User)

    // 结构体指针 - 可寻址
    destVal := reflect.ValueOf(&dest)
    destType := reflect.TypeOf(&dest)

    // 结构体
    destValElem := destVal.Elem()
    destTypeElem := destType.Elem()

    // 遍历结构体的所有字段
    for i := 0; i < destValElem.NumField(); i++ {
        destValField := destValElem.Field(i)
        destTypeField := destTypeElem.Field(i)

        switch destValField.Kind() {
        // 字段是一个结构体
        case reflect.Struct:
            // 递归
            fmt.Println("IS A STRUCT")
        default:
            // 获取完整的标签
            tag := destTypeField.Tag
            if len(tag) == 0 {
                continue
            }

            // 获取自定义标签键的值
            tagVal := tag.Get(selector)
            if len(tagVal) == 0 {
                continue
            }

            // 访问源数据中的值,通过点表示法访问 - 例如:"user.profile.firstname"
            sourceValue := "John" // tee.Get(sourceInterface, tagVal)

            // 导出字段
            f := destValField
            if f.IsValid() {
                // 只有可寻址且不是通过未导出的结构体字段获得的值才能更改
                if f.CanSet() {
                    // 更改 Name 的值
                    fv := reflect.ValueOf(sourceValue)
                    destValField.Set(fv)
                }
            }
            fmt.Println("NOT STRUCT")
        }
    }

    fmt.Println(dest.Name)
}

问题出在以下这行代码,因为我不应该知道 destinationInterface 要转换为 User

如何动态地将接口转换为在运行时定义的未知类型,或者以其他方式获得更新后的 Name 的输出为 "John" 而不是 "Sebastien"?

dest := destinationInterface.(User)

以下是在 golang playgound 上运行的完整代码:

https://play.golang.org/p/sYvz-Fwp97P

英文:

Let's say I have a struct User

type User struct {
Name     string `owm:&quot;newNameFromAPI&quot;`
}

The code below initialises the struct and passes it to a function

func main() {	
dest := User{
Name: &quot;Sebastien&quot;,
}
deepUpdate(dest, &quot;owm&quot;)
}

The function uses reflect in order to iterate over all the fields of the struct and update them accordingly (several chunks of code were removed for clarity)

func deepUpdate(destinationInterface interface{}, selector string) {
// Here I&#39;m not supposed to know that the type is `User`
// And if I just use dest := destinationInterface, the value won&#39;t update
dest := destinationInterface.(User)
// Pointer to struct - addressable
destVal := reflect.ValueOf(&amp;dest)
destType := reflect.TypeOf(&amp;dest)
// psindirect := reflect.Indirect(destVal) // ? ValueOf(&lt;Ptr Value&gt;) Requires to be adressed via reflect.Indirect() to access Field - https://stackoverflow.com/a/50098755/9077800
// Struct
destValElem := destVal.Elem()
destTypeElem := destType.Elem()
// Iterate over all fields of the struct
for i := 0; i &lt; destValElem.NumField(); i++ {
// // for i := 0; i &lt; destTypeElem.NumField(); i++ {
destValField := destValElem.Field(i)
destTypeField := destTypeElem.Field(i)
switch destValField.Kind() {
// Field is a struct
case reflect.Struct:
// Recursion
fmt.Println(&quot;IS A STRUCT&quot;)
default:
// Get the complete tag
tag := destTypeField.Tag
if len(tag) == 0 {
continue
}
// Get the value of our custom tag key
tagVal := tag.Get(selector)
if len(tagVal) == 0 {
continue
}
// Access the value in our source data, thanks to a dot notation access - example: &quot;user.profile.firstname&quot;
sourceValue := &quot;John&quot; // tee.Get(sourceInterface, tagVal)
// Exported field
f := destValField
if f.IsValid() {
// A Value can be changed only if it is
// addressable and was not obtained by
// the use of unexported struct fields.
if f.CanSet() {
// Change value of Name
fv := reflect.ValueOf(sourceValue)
destValField.Set(fv)
}
}
fmt.Println(&quot;NOT STRUCT&quot;)
}
}
fmt.Println(dest.Name)
}

The problem is the following line, because I'm not supposed to know that the destinationInterface is to be casted to User.

How can I dynamically cast the interface to some unknown type defined at runtime, or any other way to get the same output of the updated Name "John" instead of "Sebastien"?

dest := destinationInterface.(User)

Here is the complete code running on the golang playgound

https://play.golang.org/p/sYvz-Fwp97P

答案1

得分: 1

你不需要知道dest的类型。这个示例不是递归的,但可以很容易地升级。

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Name string `owm:"newNameFromAPI"`
}

func main() {
	dest := User{
		Name: "Sebastien",
	}

	fmt.Println("Before:", dest.Name)
	deepUpdate(&dest, "owm")
	fmt.Println("After:", dest.Name)
}

func deepUpdate(dest interface{}, selector string) {
	// 获取dest的反射值
	rv := reflect.ValueOf(dest)
	// 解引用每个指针
	for rv.Kind() == reflect.Ptr {
		rv = reflect.Indirect(rv)
	}

	// 检查是否为结构体,应该使用panic或返回错误
	if reflect.TypeOf(rv.Interface()).Kind() != reflect.Struct {
		fmt.Println("NOT A STRUCT")
		return
	}

	// 遍历字段
	for i := 0; i < rv.NumField(); i++ {
		// 获取标签值
		tag := rv.Type().Field(i).Tag.Get(selector)
		if tag == "" {
			continue
		}

		// 获取源值
		sourceValue := "John"

		// 将源值分配给dest对应的字段
		if rv.Field(i).CanSet() {
			rv.Field(i).Set(reflect.ValueOf(sourceValue))
		}
	}
}

唯一的要求是sourceValue必须与相应字段的类型相同。

工作示例:https://goplay.space/#D0CmTaS5AiP

英文:

You don't have to know the type of dest. The example is not recursive, but it can be easily upgraded.

package main
import (
&quot;fmt&quot;
&quot;reflect&quot;
)
type User struct {
Name string `owm:&quot;newNameFromAPI&quot;`
}
func main() {
dest := User{
Name: &quot;Sebastien&quot;,
}
fmt.Println(&quot;Before:&quot;, dest.Name)
deepUpdate(&amp;dest, &quot;owm&quot;)
fmt.Println(&quot;After:&quot;, dest.Name)
}
func deepUpdate(dest interface{}, selector string) {
//Get the reflect value of dest
rv := reflect.ValueOf(dest)
//Dereference every pointers
for rv.Kind() == reflect.Ptr {
rv = reflect.Indirect(rv)
}
//Check if its a struct, should use panic or return error
if reflect.TypeOf(rv.Interface()).Kind() != reflect.Struct {
fmt.Println(&quot;NOT A STRUCT&quot;)
return
}
//Loop over the fields
for i := 0; i &lt; rv.NumField(); i++ {
//Get the tag value
tag := rv.Type().Field(i).Tag.Get(selector)
if tag == &quot;&quot; {
continue
}
//Get the source
sourceValue := &quot;John&quot;
//Assign the source to the dest&#39;s corresponding field
if rv.Field(i).CanSet() {
rv.Field(i).Set(reflect.ValueOf(sourceValue))
}
}
}

The only thing is that you have to use the same type for sourceValue that the corresponding field is.

Working example: https://goplay.space/#D0CmTaS5AiP

huangapple
  • 本文由 发表于 2021年8月12日 03:03:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/68747615.html
匿名

发表评论

匿名网友

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

确定