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