fox/mapstruct/map2struct.go

154 lines
3.8 KiB
Go
Raw Normal View History

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())
}