英文:
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:"newNameFromAPI"`
}
The code below initialises the struct and passes it to a function
func main() {
dest := User{
Name: "Sebastien",
}
deepUpdate(dest, "owm")
}
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'm not supposed to know that the type is `User`
// And if I just use dest := destinationInterface, the value won't update
dest := destinationInterface.(User)
// Pointer to struct - addressable
destVal := reflect.ValueOf(&dest)
destType := reflect.TypeOf(&dest)
// psindirect := reflect.Indirect(destVal) // ? ValueOf(<Ptr Value>) 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 < destValElem.NumField(); i++ {
// // for i := 0; i < destTypeElem.NumField(); i++ {
destValField := destValElem.Field(i)
destTypeField := destTypeElem.Field(i)
switch destValField.Kind() {
// Field is a struct
case reflect.Struct:
// Recursion
fmt.Println("IS A STRUCT")
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: "user.profile.firstname"
sourceValue := "John" // 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("NOT STRUCT")
}
}
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
答案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 (
"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) {
//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("NOT A STRUCT")
return
}
//Loop over the fields
for i := 0; i < rv.NumField(); i++ {
//Get the tag value
tag := rv.Type().Field(i).Tag.Get(selector)
if tag == "" {
continue
}
//Get the source
sourceValue := "John"
//Assign the source to the dest'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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论