使用反射在Go语言中递归迭代结构体和集合字段。

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

recursively iterating struct and set fields with go using reflect

问题

我想构建一个使用反射设置结构体字段的程序。我已经成功实现了顶层字段的设置,但是在处理嵌套结构体字段时遇到了困难。如何迭代嵌套结构体字段?

type Payload struct {
	Type    string   `json:"type"`
	SubItem *SubItem `json:"sub_item"`
}

type SubItem struct {
	Foo string `json:"foo"`
}

func main() {
	var payload Payload
	setValue(&payload, "type", "test1")
	setValue(&payload, "sub_item.foo", "test2")
}

func setValue(structPtr interface{}, key string, value string) {
	structValue := reflect.Indirect(reflect.ValueOf(structPtr))
	for i, subkey := range strings.Split(key, ".") {
		isLast := i == len(strings.Split(key, "."))-1
		var found bool
		// 这一行会报错:"reflect: call of reflect.Value.NumField on zero Value"
		for i := 0; i < structValue.NumField(); i++ {
			field := structValue.Type().Field(i)
			jsonTags := strings.Split(field.Tag.Get("json"), ",")
			if jsonTags[0] == subkey {
				found = true
				if isLast {
					if isLast {
						// 最后一个元素
						// TODO 设置值
						fmt.Printf("TODO 设置值 %s 到 %v", value, structValue)
						structValue = reflect.Indirect(reflect.ValueOf(structPtr))
					}
				} else {
					structValue = reflect.Indirect(reflect.ValueOf(structValue.Field(i).Interface()))
				}
				break
			}
		}
		if !found {
			panic(fmt.Errorf("未找到字段 %s", key))
		}
	}
}

以上是你提供的代码的翻译。

英文:

I want to build the program that sets field of struct using reflection. I made it work for top-level field but I am struggling with nested struct field. How can iterate over nested struct field?

type Payload struct {
Type    string   `json:&quot;type&quot;`
SubItem *SubItem `json:&quot;sub_item&quot;`
}
type SubItem struct {
Foo string `json:&quot;foo&quot;`
}
func main() {
var payload Payload
setValue(&amp;payload, &quot;type&quot;, &quot;test1&quot;)
setValue(&amp;payload, &quot;sub_item.foo&quot;, &quot;test2&quot;)
}
func setValue(structPtr interface{}, key string, value string) {
structValue := reflect.Indirect(reflect.ValueOf(structPtr))
for i, subkey := range strings.Split(key, &quot;.&quot;) {
isLast := i == len(strings.Split(key, &quot;.&quot;))-1
var found bool
// this line is crashing with &quot;reflect: call of reflect.Value.NumField on zero Value&quot;
for i := 0; i &lt; structValue.NumField(); i++ {
field := structValue.Type().Field(i)
jsonTags := strings.Split(field.Tag.Get(&quot;json&quot;), &quot;,&quot;)
if jsonTags[0] == subkey {
found = true
if isLast {
if isLast {
// last element
// TODO set value
fmt.Printf(&quot;TODO set value %s to %v&quot;, value, structValue)
structValue = reflect.Indirect(reflect.ValueOf(structPtr))
}
} else {
structValue = reflect.Indirect(reflect.ValueOf(structValue.Field(i).Interface()))
}
break
}
}
if !found {
panic(fmt.Errorf(&quot;failed to find field %s&quot;, key))
}
}
}

答案1

得分: 4

请使用以下函数:

func setValue(p interface{}, key string, value interface{}) {
	v := reflect.ValueOf(p)

	// 通过 key 中的名称循环查找目标字段。
	for _, name := range strings.Split(key, ".") {

		// 如果值是指针,则
		// - 如果 ptr 为 nil,则分配值。
		// - 解引用 ptr
		for v.Kind() == reflect.Ptr {
			if v.IsNil() {
				v.Set(reflect.New(v.Type().Elem()))
			}
			v = v.Elem()
		}

		// 我们期望值是结构体。查找具名字段。
		v = findJSONField(v, name)
		if !v.IsValid() {
			panic(fmt.Sprintf("无法找到字段 %s", key))
		}
	}

	// 设置字段的值。
	v.Set(reflect.ValueOf(value))
}

// 函数 findJSONField 通过字段的 JSON 标签查找结构体字段。
func findJSONField(v reflect.Value, name string) reflect.Value {
	t := v.Type()
	for i := 0; i < v.NumField(); i++ {
		if tag, _, _ := strings.Cut(t.Field(i).Tag.Get("json"), ","); tag == name {
			return v.Field(i)
		}
	}
	return reflect.Value{}
}

你可以在这里查看代码:https://go.dev/play/p/DnSuydQgQt9

英文:

Use this function:

func setValue(p interface{}, key string, value interface{}) {
v := reflect.ValueOf(p)
// Loop through the names in key to find the target field.
for _, name := range strings.Split(key, &quot;.&quot;) {
// If the value is pointer, then
// - allocate value if ptr is nil.
// - indirect ptr
for v.Kind() == reflect.Ptr {
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
v = v.Elem()
}
// We expect that the value is struct. Find the 
// named field.
v = findJSONField(v, name)
if !v.IsValid() {
panic(fmt.Sprintf(&quot;could not find field %s&quot;, key))
}
}
// Set the field.
v.Set(reflect.ValueOf(value))
}

The function findJSONField finds a struct field by the field's JSON tag:

func findJSONField(v reflect.Value, name string) reflect.Value {
t := v.Type()
for i := 0; i &lt; v.NumField(); i++ {
if tag, _, _ := strings.Cut(t.Field(i).Tag.Get(&quot;json&quot;), &quot;,&quot;); tag == name {
return v.Field(i)
}
}
return reflect.Value{}
}

https://go.dev/play/p/DnSuydQgQt9

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

发表评论

匿名网友

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

确定