package mapstruct import ( "errors" "fmt" "reflect" "strings" "time" ) // ToStruct 将map[string]string转换为结构体 func ToStruct(m map[string]string, out any, opts ...Option) error { // 初始化默认选项 options := defaultOptions() // 应用所有提供的选项 for _, opt := range opts { opt(options) } // 检查输出参数是否有效 if out == nil { return errors.New("输出参数不能为nil") } // 获取反射值 v := reflect.ValueOf(out) // 检查是否为指针且非nil if v.Kind() != reflect.Ptr || v.IsNil() { return errors.New("输出参数必须是非nil指针") } // 获取指向的值 v = v.Elem() // 检查是否为结构体 if v.Kind() != reflect.Struct { return errors.New("输出参数必须指向结构体") } // 执行转换 return fillStructFromMap(m, v, options) } // fillStructFromMap 从map填充结构体字段 func fillStructFromMap(m map[string]string, v reflect.Value, opts *Options) error { // 获取结构体类型信息 t := v.Type() // 遍历所有字段 for i := 0; i < v.NumField(); i++ { field := t.Field(i) // 获取字段信息 fieldValue := v.Field(i) // 获取字段值 // 跳过不可设置的字段(非导出字段) if !fieldValue.CanSet() { continue } // 获取字段映射名称 name, skip := getFieldName(field, opts.TagName) if skip { continue // 跳过标记为"-"的字段 } // 从map中获取值 value, ok := m[name] if !ok { continue // map中没有对应字段则跳过 } // 设置字段值 if err := setField(m, fieldValue, field, value, opts); err != nil { return fmt.Errorf("设置字段%s失败: %w", name, err) } } return nil } // getFieldName 从结构体字段获取映射名称 func getFieldName(field reflect.StructField, tagName string) (string, bool) { // 查找指定标签 if tag, ok := field.Tag.Lookup(tagName); ok { if tag == "-" { return "", true // 标记为跳过 } // 处理标签中的选项(如omitempty) if commaIdx := strings.Index(tag, ","); commaIdx != -1 { tag = tag[:commaIdx] } if tag != "" { return tag, false // 使用标签指定的名称 } } return field.Name, false // 默认使用字段名 } // setField 设置结构体字段值 func setField(m map[string]string, fieldValue reflect.Value, field reflect.StructField, value string, opts *Options) error { // 处理指针类型 if fieldValue.Kind() == reflect.Ptr { if strings.TrimSpace(value) == "" { // 空字符串表示nil指针 fieldValue.Set(reflect.Zero(field.Type)) return nil } // 创建新实例(如果指针为nil) if fieldValue.IsNil() { fieldValue.Set(reflect.New(field.Type.Elem())) } // 递归处理指向的值 return setField(m, fieldValue.Elem(), field, value, opts) } // 应用转换钩子或基本类型转换 result, err := applyMapToStructHook(value, field, opts) if err != nil { return err } // 处理嵌套结构体 if fieldValue.Kind() == reflect.Struct && fieldValue.Type() != reflect.TypeOf(time.Time{}) { // 创建嵌套map(过滤出以当前字段名为前缀的键) nestedMap := make(map[string]string) name, _ := getFieldName(field, opts.TagName) prefix := name + "." for k, v := range m { if strings.HasPrefix(k, prefix) { // 去掉前缀后作为嵌套字段名 nestedMap[strings.TrimPrefix(k, prefix)] = v } } // 如果嵌套map不为空,则递归处理 if len(nestedMap) > 0 { return fillStructFromMap(nestedMap, fieldValue, opts) } return nil } // 设置最终值 if result != nil { rv := reflect.ValueOf(result) // 检查类型是否可转换 if rv.Type().ConvertibleTo(fieldValue.Type()) { fieldValue.Set(rv.Convert(fieldValue.Type())) return nil } } return fmt.Errorf("无法将 %q 转换为 %v 类型", value, fieldValue.Type()) }