英文:
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("not a struct")
}
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("Found field: " + 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, "_")
// 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 < 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 < 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 -> 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论