通过反射创建结构体实例并设置值

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

Create instance of struct via reflection and set values

问题

我尝试做什么

我试图传递一个包括json标签的struct实例给一个func,创建一个新的实例,并在字段上设置
之后,我尝试序列化(JSON),但值为空。

注意:我查阅了很多关于通过反射设置值的文章,但似乎我错过了一些细节。

struct定义

这部分定义了带有json和xml标签的结构体

type Person struct {
    Name string `json:"Name" xml:"Person>FullName"`
    Age  int    `json:"Age" xml:"Person>Age"`
}

创建实例(+包装到空接口中)

之后,我创建一个实例并将其存储在interface{}中 - 为什么?因为在我的生产代码中,这些东西将在接受interface{}func中完成。

var iFace interface{} = Person{
    Name: "Test",
    Age:  666,
}

创建结构体的新实例并通过反射设置值

iFaceType := reflect.TypeOf(iFace)
item := reflect.New(iFaceType)
s := item.Elem()
if s.Kind() == reflect.Struct {
    fName := s.FieldByName("Name")
    if fName.IsValid() {
        // 只有当Value是可寻址的并且不是通过未导出的结构字段获得时,才能更改Value。
        if fName.CanSet() {
            // 更改N的值
            switch fName.Kind() {
            case reflect.String:
                fName.SetString("reflectedNameValue")
                fmt.Println("Name was set to reflectedNameValue")
            }
        }
    }
    fAge := s.FieldByName("Age")
    if fAge.IsValid() {
        // 只有当Value是可寻址的并且不是通过未导出的结构字段获得时,才能更改Value。
        if fAge.CanSet() {
            // 更改N的值
            switch fAge.Kind() {
            case reflect.Int:
                x := int64(42)
                if !fAge.OverflowInt(x) {
                    fAge.SetInt(x)
                    fmt.Println("Age was set to", x)
                }
            }
        }
    }
}

问题

我做错了什么?
在生产代码中,我用数据填充多个副本并将其添加到slice中...
但只有在json标签保持不变且序列化方式完全相同的情况下,这才有意义。

用于测试的代码示例

package main

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

func main() {
    type Person struct {
        Name string `json:"Name" xml:"Person>FullName"`
        Age  int    `json:"Age" xml:"Person>Age"`
    }

    var iFace interface{} = Person{
        Name: "Test",
        Age:  666,
    }
    fmt.Println("normal: \n" + JSONify(iFace))
    iFaceType := reflect.TypeOf(iFace)
    item := reflect.New(iFaceType)
    s := item.Elem()
    if s.Kind() == reflect.Struct {
        fName := s.FieldByName("Name")
        if fName.IsValid() {
            // 只有当Value是可寻址的并且不是通过未导出的结构字段获得时,才能更改Value。
            if fName.CanSet() {
                // 更改N的值
                switch fName.Kind() {
                case reflect.String:
                    fName.SetString("reflectedNameValue")
                    fmt.Println("Name was set to reflectedNameValue")
                }
            }
        }
        fAge := s.FieldByName("Age")
        if fAge.IsValid() {
            // 只有当Value是可寻址的并且不是通过未导出的结构字段获得时,才能更改Value。
            if fAge.CanSet() {
                // 更改N的值
                switch fAge.Kind() {
                case reflect.Int:
                    x := int64(42)
                    if !fAge.OverflowInt(x) {
                        fAge.SetInt(x)
                        fmt.Println("Age was set to", x)
                    }
                }
            }
        }
    }
    fmt.Println("reflected: \n" + JSONify(item))
}

func JSONify(v interface{}) string {
    var bytes []byte
    bytes, _ = json.MarshalIndent(v, "", "\t")
    return string(bytes)
}

英文:

what I try to do

I try to pass an instance of a struct - including json tags to a func, create a new instance, and set value on field
after this i try to serialize (JSON), but the values are empty

NOTICE: i looked up loads of articles on SO about setting values via reflection, but it seems i missed a little detail

struct definition

this part defines the struct with json and xml tags

type Person struct {
    Name string `json:"Name" xml:"Person>FullName"`
    Age  int    `json:"Age" xml:"Person>Age"`
}

create instance (+wrapping into empty interface)

afterwards I create an instance and store it in an interface{} - why? because in my production code this stuff will be done in a func which accepts a interface{}

var iFace interface{} = Person{
		Name: "Test",
		Age:  666,
	}

creating a new instance of the struct and setting values via reflection

iFaceType := reflect.TypeOf(iFace)
	item := reflect.New(iFaceType)
	s := item.Elem()
	if s.Kind() == reflect.Struct {
		fName := s.FieldByName("Name")
		if fName.IsValid() {
			// A Value can be changed only if it is
			// addressable and was not obtained by
			// the use of unexported struct fields.
			if fName.CanSet() {
				// change value of N
				switch fName.Kind() {
				case reflect.String:
					fName.SetString("reflectedNameValue")
					fmt.Println("Name was set to reflectedNameValue")
				}
			}
		}
		fAge := s.FieldByName("Age")
		if fAge.IsValid() {
			// A Value can be changed only if it is
			// addressable and was not obtained by
			// the use of unexported struct fields.
			if fAge.CanSet() {
				// change value of N
				switch fAge.Kind() {
				case reflect.Int:
					x := int64(42)
					if !fAge.OverflowInt(x) {
						fAge.SetInt(x)
						fmt.Println("Age was set to", x)
					}
				}
			}
		}
	}

Question

what am I doing wrong?
in production code I fill multiple copies with data and add it to a slice...
but this only makes sense if the json tags are kept in place and the stuff serializes just the same way.

code sample for play

package main

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

func main() {
	type Person struct {
		Name string `json:"Name" xml:"Person>FullName"`
		Age  int    `json:"Age" xml:"Person>Age"`
	}

	var iFace interface{} = Person{
		Name: "Test",
		Age:  666,
	}
	fmt.Println("normal: \n" + JSONify(iFace))
	iFaceType := reflect.TypeOf(iFace)
	item := reflect.New(iFaceType)
	s := item.Elem()
	if s.Kind() == reflect.Struct {
		fName := s.FieldByName("Name")
		if fName.IsValid() {
			// A Value can be changed only if it is
			// addressable and was not obtained by
			// the use of unexported struct fields.
			if fName.CanSet() {
				// change value of N
				switch fName.Kind() {
				case reflect.String:
					fName.SetString("reflectedNameValue")
					fmt.Println("Name was set to reflectedNameValue")
				}
			}
		}
		fAge := s.FieldByName("Age")
		if fAge.IsValid() {
			// A Value can be changed only if it is
			// addressable and was not obtained by
			// the use of unexported struct fields.
			if fAge.CanSet() {
				// change value of N
				switch fAge.Kind() {
				case reflect.Int:
					x := int64(42)
					if !fAge.OverflowInt(x) {
						fAge.SetInt(x)
						fmt.Println("Age was set to", x)
					}
				}
			}
		}
	}
	fmt.Println("reflected: \n" + JSONify(item))
}

func JSONify(v interface{}) string {
	var bytes []byte
	bytes, _ = json.MarshalIndent(v, "", "\t")
	return string(bytes)
}

答案1

得分: 3

您的item是类型为reflect.Value。您需要调用Value.Interface()来获取其中包装的值:

fmt.Println("reflected: \n" + JSONify(item.Interface()))

通过这个更改,输出将会是(在Go Playground上尝试):

normal: 
{
    "Name": "Test",
    "Age": 666
}
Name was set to reflectedNameValue
Age was set to 42
reflected: 
{
    "Name": "reflectedNameValue",
    "Age": 42
}

reflect.Value本身也是一个结构体,但显然尝试将其编组为JSON与编组Person结构体值不同。reflect.Value不实现将包装数据编组为JSON的功能。

英文:

Your item is of type reflect.Value. You have to call Value.Interface() to obtain the value wrapped in it:

fmt.Println("reflected: \n" + JSONify(item.Interface()))

With this change, output will be (try it on the Go Playground):

normal: 
{
"Name": "Test",
"Age": 666
}
Name was set to reflectedNameValue
Age was set to 42
reflected: 
{
"Name": "reflectedNameValue",
"Age": 42
}

reflect.Value itself is also a struct, but obviously trying to marshal it will not be identical to marshaling a Person struct value. reflect.Value does not implement marshaling the wrapped data to JSON.

huangapple
  • 本文由 发表于 2021年12月2日 23:31:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/70202238.html
匿名

发表评论

匿名网友

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

确定