递归解析字典键中的结构体

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

Recursively parse over struct from dict keys

问题

我正在尝试递归解析一个表单数据字典到嵌套结构中。它有点工作,但最终只有一个字段被设置。我发现该字段一直被旧结构覆盖,但需要一些帮助找到解决方案。

函数的入口点:

func FormDataToStruct(data map[string]string, s any) {
	// 将表单数据解析到结构体中
	v := reflect.ValueOf(s)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		panic("not a struct")
	}
	formParse(data, v, s)
}

设置顶层结构体的值:

func formParse(data map[string]string, v reflect.Value, s any) {
	for key, value := range data {
		if hasField(v, key) {
			println("Found field: " + key)
			var val, err = TransformValue(s, key, value)
			if err != nil {
				panic(err)
			}
			SetValue(s, key, val)
			delete(data, key)
		}
	}
    // 设置内部结构体的值
	for key, value := range data {
		keys := strings.Split(key, "_")
		// 递归使用另一个函数解析内部结构体
		recurseKeys(keys, value, v, s, s)
	}
}

结构体如下所示:

type Me struct {
	Name string
	Age  int
	Friend struct {
		Name string
		Age  int
	}
}

要解析的表单数据如下所示:

map[name:John age:20 friend_name:Jane friend_age:20]
func recurseKeys(keys []string, value string, v reflect.Value, s any, parent any) {
	if len(keys) == 1 {
		// 我们到达了键的末尾
		// 设置值
		var val, err = TransformValue(s, keys[0], value)
		if err != nil {
			panic(err)
		}
		SetValue(s, keys[0], val)
		return
	}
	// 我们还没有到达键的末尾
	// 我们需要遍历结构体
	for i := 0; i < v.NumField(); i++ {
		if strings.EqualFold(v.Type().Field(i).Name, keys[0]) {
			// 我们找到了字段
			// 使用下一个键进行递归
			newS := reflect.New(v.Field(i).Type())
			recurseKeys(keys[1:], value, v.Field(i), newS.Interface(), parent)
			// 检查旧结构体上的字段是否为指针,如果是,我们需要设置指针
			// 如果不是指针,我们需要设置值
			if v.Field(i).Kind() == reflect.Ptr {
				// 检查newS是否为指针
				if newS.Kind() == reflect.Ptr {
					v.Field(i).Set(newS)
				} else {
					v.Field(i).Set(newS.Elem())
				}
			} else {
				// 检查newS是否为指针
				if newS.Kind() == reflect.Ptr {
					v.Field(i).Set(newS.Elem())
				} else {
					v.Field(i).Set(newS)
				}
			}
		}
	}
}

通过上述结构体运行上述表单数据将导致以下输出:

println(meInstance)
// {John 20 {Jane 0}}

非常感谢任何帮助!

编辑

具有最小可重现示例的Go Playground:
https://go.dev/play/p/d5pIK3uQrUL

英文:

I am trying to recursively parse a form data dictionary into an embedded struct.
It is kindof working, but eventually only one field gets set. I figured that the field keeps being overridden by the old struct, but need some help finding the solution.

Entrypoint of the function:

func FormDataToStruct(data map[string]string, s any) {
	// Parse the form data into the struct
	v := reflect.ValueOf(s)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		panic(&quot;not a struct&quot;)
	}
	formParse(data, v, s)
}

Set the values on the topmost struct:

func formParse(data map[string]string, v reflect.Value, s any) {
	for key, value := range data {
		if hasField(v, key) {
			println(&quot;Found field: &quot; + key)
			var val, err = TransformValue(s, key, value)
			if err != nil {
				panic(err)
			}
			SetValue(s, key, val)
			delete(data, key)
		}
	}
    // Set values of inner structs
	for key, value := range data {
		keys := strings.Split(key, &quot;_&quot;)
		// Parse the inner struct recursively with another function
		recurseKeys(keys, value, v, s, s)
	}
}

Struct looks like this:

	type Me struct {
		Name string
		Age  int
		Friend struct {
			Name string
			Age  int
		}
	}

Form data to parse looks like this:

map[name:John age:20 friend_name:Jane friend_age:20]
func recurseKeys(keys []string, value string, v reflect.Value, s any, parent any) {
	if len(keys) == 1 {
		// We are at the end of the keys
		// Set the value
		var val, err = TransformValue(s, keys[0], value)
		if err != nil {
			panic(err)
		}
		SetValue(s, keys[0], val)
		return
	}
	// We are not at the end of the keys
	// We need to iterate over the struct
	for i := 0; i &lt; v.NumField(); i++ {
		if strings.EqualFold(v.Type().Field(i).Name, keys[0]) {
			// We found the field
			// Recurse with the next key
			newS := reflect.New(v.Field(i).Type())
			recurseKeys(keys[1:], value, v.Field(i), newS.Interface(), parent)
			// Check if the field on the old struct is a pointer, if it is, we need to set the pointer
			// If it is not a pointer, we need to set the value
			if v.Field(i).Kind() == reflect.Ptr {
				// Check if newS is a pointer
				if newS.Kind() == reflect.Ptr {
					v.Field(i).Set(newS)
				} else {
					v.Field(i).Set(newS.Elem())
				}
			} else {
				// Check if newS is a pointer
				if newS.Kind() == reflect.Ptr {
					v.Field(i).Set(newS.Elem())
				} else {
					v.Field(i).Set(newS)
				}
			}
		}
	}
}

Running the above form data through the struct would result in the following output:

println(meInstance)
// {John 20 {Jane 0}}

Any help is very much appreciated!

EDIT

Go playground with minimal reproducible example:
https://go.dev/play/p/d5pIK3uQrUL

答案1

得分: 1

这段代码的问题在于它创建了一个新的结构体,并用它替换了现有的对象。因此,你总是会看到最后分配的值。

修复后的代码如下所示:

func recurseKeys(keys []string, value string, v reflect.Value, s any, parent any) {
	if len(keys) == 1 {
		// 我们已经到达了键的末尾
		// 设置值
		var val, err = TransformValue(s, keys[0], value)
		if err != nil {
			panic(err)
		}
		SetValue(s, keys[0], val)
		return
	}
	// 我们还没有到达键的末尾
	// 我们需要遍历结构体
	for i := 0; i < v.NumField(); i++ {
		if strings.EqualFold(v.Type().Field(i).Name, keys[0]) {
			// 我们找到了字段
			// 用下一个键递归
			if v.Field(i).IsZero() {
				var newS reflect.Value
				if v.Field(i).Kind() == reflect.Ptr {
					newS = reflect.New(v.Field(i).Type().Elem()).Elem()
				} else {
					newS = reflect.New(v.Field(i).Type())
				}

				// 检查旧结构体上的字段是否是指针,如果是,我们需要设置指针
				// 如果不是指针,我们需要设置值
				if v.Field(i).Kind() == reflect.Ptr {
					v.Field(i).Set(newS.Addr())
				} else {
					v.Field(i).Set(newS.Elem())
				}

			}
			if v.Field(i).Kind() == reflect.Ptr {
				recurseKeys(keys[1:], value, v.Field(i), v.Field(i).Interface(), parent)
			} else {
				recurseKeys(keys[1:], value, v.Field(i), v.Field(i).Addr().Interface(), parent)
			}

		}
	}
}

这段代码也接受结构体指针。

性能改进提示:你可以考虑扫描目标对象并创建一个name -> Reflect value的映射,以减少循环的次数。

维护提示:最好考虑使用结构体标签,而不是直接反射结构体变量的名称。

英文:

The issue with this code is that it creates a new struct and replaces it with the existing object. Therefore, you will always see the last assigned value.

The fixed version of the code will be like below:

func recurseKeys(keys []string, value string, v reflect.Value, s any, parent any) {
	if len(keys) == 1 {
		// We are at the end of the keys
		// Set the value
		var val, err = TransformValue(s, keys[0], value)
		if err != nil {
			panic(err)
		}
		SetValue(s, keys[0], val)
		return
	}
	// We are not at the end of the keys
	// We need to iterate over the struct
	for i := 0; i &lt; v.NumField(); i++ {
		if strings.EqualFold(v.Type().Field(i).Name, keys[0]) {
			// We found the field
			// Recurse with the next key
			if v.Field(i).IsZero() {
				var newS reflect.Value
				if v.Field(i).Kind() == reflect.Ptr {
					newS = reflect.New(v.Field(i).Type().Elem()).Elem()
				} else {
					newS = reflect.New(v.Field(i).Type())
				}

				// Check if the field on the old struct is a pointer, if it is, we need to set the pointer
				// If it is not a pointer, we need to set the value
				if v.Field(i).Kind() == reflect.Ptr {
					v.Field(i).Set(newS.Addr())
				} else {
					v.Field(i).Set(newS.Elem())
				}

			}
			if v.Field(i).Kind() == reflect.Ptr {
				recurseKeys(keys[1:], value, v.Field(i), v.Field(i).Interface(), parent)
			} else {
				recurseKeys(keys[1:], value, v.Field(i), v.Field(i).Addr().Interface(), parent)
			}

		}
	}
}

This code accepts struct pointers as well.

Performance improvement tip: You may want to consider scanning the target object and creating a map of name -&gt; Reflect value to reduce the number of loops.

Maintenance tip: It's better to consider using struct tags instead of reflecting the struct Variable name directly.

huangapple
  • 本文由 发表于 2022年12月7日 16:14:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/74713578.html
匿名

发表评论

匿名网友

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

确定