package convert import ( "encoding/json" "reflect" "strings" "sync" "sync/atomic" "apigo.cc/go/cast" "gopkg.in/yaml.v3" ) // To 将 source 中的数据深度映射到 destination 中。 // 核心哲学:意图优先。根据 destination 的类型推断用户的意图,并尽力转化数据。 func To(source, destination any) { if destination == nil { return } convertedResult := performConversion(source, destination) if convertedResult != nil { destinationValue := reflect.ValueOf(destination) // 循环解开指针直到目标值,在此过程中自动初始化 nil 指针 for destinationValue.Kind() == reflect.Ptr { if destinationValue.IsNil() && destinationValue.CanSet() { destinationValue.Set(reflect.New(destinationValue.Type().Elem())) } destinationValue = destinationValue.Elem() } if destinationValue.CanSet() { destinationValue.Set(*convertedResult) } } } func performConversion(source, destination any) *reflect.Value { var sourceValue reflect.Value var destinationValue reflect.Value if val, ok := source.(reflect.Value); ok { sourceValue = val } else { sourceValue = reflect.ValueOf(source) } if val, ok := destination.(reflect.Value); ok { destinationValue = val } else { destinationValue = reflect.ValueOf(destination) } // 1. 初始化目标容器 ensureInitialized(destinationValue) // 2. 获取底层业务数据 realSource := cast.RealValue(sourceValue) realDestination := cast.RealValue(destinationValue) if !realDestination.IsValid() { return nil } // 3. 处理 Unmarshaler 接口 if realDestination.CanAddr() { address := realDestination.Addr().Interface() if unmarshaler, isJSONUnmarshaler := address.(json.Unmarshaler); isJSONUnmarshaler { _ = unmarshaler.UnmarshalJSON(cast.MustJsonBytes(realSource.Interface())) return nil } if unmarshaler, isYAMLUnmarshaler := address.(yaml.Unmarshaler); isYAMLUnmarshaler { _ = unmarshaler.UnmarshalYAML(&yaml.Node{Value: cast.String(realSource.Interface())}) return nil } } // 4. 核心转换逻辑 sourceType := getActualType(realSource) destinationType := realDestination.Type() // 兼容 interface{} 目标 if destinationType.Kind() == reflect.Interface { if realDestination.CanSet() { realDestination.Set(reflect.ValueOf(realSource.Interface())) return nil } } // 极致去摩擦:如果目标是单值,但输入是切片,自动取第一个元素进行后续处理 effectiveSource := realSource if sourceType.Kind() == reflect.Slice && realSource.Len() > 0 && destinationType.Kind() != reflect.Slice && destinationType.Kind() != reflect.Array { effectiveSource = cast.RealValue(realSource.Index(0)) } var allocatedValue *reflect.Value switch destinationType.Kind() { case reflect.Bool: applyValue(realDestination, reflect.ValueOf(cast.Bool(effectiveSource.Interface())), &allocatedValue) case reflect.String: applyValue(realDestination, reflect.ValueOf(cast.String(effectiveSource.Interface())), &allocatedValue) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: applyValue(realDestination, reflect.ValueOf(cast.Int64(effectiveSource.Interface())).Convert(destinationType), &allocatedValue) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: applyValue(realDestination, reflect.ValueOf(cast.Uint64(effectiveSource.Interface())).Convert(destinationType), &allocatedValue) case reflect.Float32, reflect.Float64: applyValue(realDestination, reflect.ValueOf(cast.Float64(effectiveSource.Interface())).Convert(destinationType), &allocatedValue) case reflect.Slice: if destinationType.Elem().Kind() == reflect.Uint8 { applyValue(realDestination, reflect.ValueOf(cast.MustJsonBytes(realSource.Interface())), &allocatedValue) } else { normalizedSource := realSource if sourceType.Kind() == reflect.String { sourceStr := realSource.String() if !strings.HasPrefix(sourceStr, "[") && strings.Contains(sourceStr, ",") { normalizedSource = reflect.ValueOf(cast.Split(sourceStr, ",")) } else if !strings.HasPrefix(sourceStr, "[") { tempSlice := reflect.MakeSlice(reflect.SliceOf(sourceType), 1, 1) tempSlice.Index(0).Set(realSource) normalizedSource = tempSlice } else { var rawList []any cast.UnJson(sourceStr, &rawList) normalizedSource = reflect.ValueOf(rawList) } } else if sourceType.Kind() != reflect.Slice { tempSlice := reflect.MakeSlice(reflect.SliceOf(sourceType), 1, 1) tempSlice.Index(0).Set(realSource) normalizedSource = tempSlice } return convertSliceToSlice(normalizedSource, realDestination) } case reflect.Struct: switch effectiveSource.Kind() { case reflect.Map: convertMapToStruct(effectiveSource, realDestination) case reflect.Struct: convertStructToStruct(effectiveSource, realDestination) case reflect.String: var rawMap map[string]any cast.UnJson(effectiveSource.String(), &rawMap) convertMapToStruct(reflect.ValueOf(rawMap), realDestination) } case reflect.Map: if realDestination.IsNil() { realDestination = reflect.MakeMap(destinationType) allocatedValue = &realDestination } switch realSource.Kind() { case reflect.Map: convertMapToMap(realSource, realDestination) case reflect.Struct: convertStructToMap(realSource, realDestination) case reflect.String: var rawMap map[string]any cast.UnJson(realSource.String(), &rawMap) convertMapToMap(reflect.ValueOf(rawMap), realDestination) } case reflect.Func: if realSource.Kind() == reflect.Func { realDestination.Set(reflect.MakeFunc(destinationType, func(args []reflect.Value) []reflect.Value { inParameters := make([]reflect.Value, 0) for i := 0; i < destinationType.NumIn(); i++ { if i < realSource.Type().NumIn() { paramPtr := reflect.New(realSource.Type().In(i)) performConversion(args[i].Interface(), paramPtr) inParameters = append(inParameters, paramPtr.Elem()) } } results := realSource.Call(inParameters) outParameters := make([]reflect.Value, 0) for i := 0; i < destinationType.NumOut(); i++ { resultPtr := reflect.New(destinationType.Out(i)) if i < len(results) { performConversion(results[i].Interface(), resultPtr) } outParameters = append(outParameters, resultPtr.Elem()) } return outParameters })) } } return allocatedValue } func applyValue(destination, value reflect.Value, allocatedValue **reflect.Value) { if destination.CanSet() { destination.Set(value) } else { *allocatedValue = &value } } func ensureInitialized(value reflect.Value) { currentType := value.Type() for currentType.Kind() == reflect.Ptr { if value.IsNil() { value.Set(reflect.New(value.Type().Elem())) } value = value.Elem() currentType = currentType.Elem() } if currentType.Kind() == reflect.Slice && value.IsNil() { value.Set(reflect.MakeSlice(value.Type(), 0, 0)) } if currentType.Kind() == reflect.Map && value.IsNil() { value.Set(reflect.MakeMap(value.Type())) } } func getActualType(value reflect.Value) reflect.Type { if !value.IsValid() { return reflect.TypeOf(nil) } actualType := value.Type() for actualType.Kind() == reflect.Ptr { actualType = actualType.Elem() } return actualType } var ( structFieldCache sync.Map structCacheCount int32 maxStructCacheSize = int32(10000) ) func toLowerCamelCase(s string) string { if len(s) == 0 { return s } for i := 0; i < len(s); i++ { if s[i] >= 'a' && s[i] <= 'z' { if i == 0 { return s } return strings.ToLower(s[:i]) + s[i:] } } return strings.ToLower(s) } func normalizeKey(key string) string { var b strings.Builder b.Grow(len(key)) for _, r := range key { if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') { b.WriteRune(r) } else if r >= 'A' && r <= 'Z' { b.WriteRune(r + 32) } } return b.String() } func buildFieldMap(t reflect.Type) map[string]int { m := make(map[string]int, t.NumField()*3) for i := 0; i < t.NumField(); i++ { field := t.Field(i) if field.Anonymous { continue } idx := i + 1 m[field.Name] = idx m[toLowerCamelCase(field.Name)] = idx m[normalizeKey(field.Name)] = idx } return m } func getFieldMap(t reflect.Type) map[string]int { if val, ok := structFieldCache.Load(t); ok { return val.(map[string]int) } m := buildFieldMap(t) if atomic.LoadInt32(&structCacheCount) < maxStructCacheSize { structFieldCache.Store(t, m) atomic.AddInt32(&structCacheCount, 1) } return m } func convertMapToStruct(sourceMap, destinationStruct reflect.Value) { fieldMap := getFieldMap(destinationStruct.Type()) for _, key := range sourceMap.MapKeys() { sourceKeyName := cast.String(key.Interface()) normalizedKey := sourceKeyName idx, ok := fieldMap[normalizedKey] if !ok { normalizedKey = toLowerCamelCase(sourceKeyName) idx, ok = fieldMap[normalizedKey] if !ok { normalizedKey = normalizeKey(sourceKeyName) idx, ok = fieldMap[normalizedKey] if !ok { continue } } } fieldIdx := idx - 1 fieldValue := destinationStruct.Field(fieldIdx) sourceValue := sourceMap.MapIndex(key) // 检查是否存在 ParseHook structType := destinationStruct.Type() fieldInfo := structType.Field(fieldIdx) if destinationStruct.CanAddr() { if method, exists := destinationStruct.Addr().Type().MethodByName("Parse" + fieldInfo.Name); exists { argumentPointer := reflect.New(method.Type.In(1)) performConversion(sourceValue.Interface(), argumentPointer) hookResults := method.Func.Call([]reflect.Value{destinationStruct.Addr(), argumentPointer.Elem()}) fieldValue.Set(hookResults[0]) continue } } convertedValue := performConversion(sourceValue.Interface(), fieldValue) if convertedValue != nil { fieldValue.Set(*convertedValue) } } } func convertStructToStruct(sourceStruct, destinationStruct reflect.Value) { destFieldMap := getFieldMap(destinationStruct.Type()) sourceType := sourceStruct.Type() for i := 0; i < sourceType.NumField(); i++ { field := sourceType.Field(i) if field.Anonymous { convertStructToStruct(sourceStruct.Field(i), destinationStruct) continue } sourceName := field.Name normalizedKey := sourceName var fieldIdx int var ok bool if fieldIdx, ok = destFieldMap[normalizedKey]; !ok { normalizedKey = toLowerCamelCase(sourceName) if fieldIdx, ok = destFieldMap[normalizedKey]; !ok { normalizedKey = normalizeKey(sourceName) if fieldIdx, ok = destFieldMap[normalizedKey]; !ok { continue } } } destFieldValue := destinationStruct.Field(fieldIdx - 1) convertedValue := performConversion(sourceStruct.Field(i).Interface(), destFieldValue) if convertedValue != nil { destFieldValue.Set(*convertedValue) } } } func convertMapToMap(sourceMap, destinationMap reflect.Value) { destinationType := destinationMap.Type() for _, key := range sourceMap.MapKeys() { newKey := reflect.New(destinationType.Key()).Elem() convertKey := performConversion(key, newKey) if convertKey != nil { newKey = *convertKey } newValue := reflect.New(destinationType.Elem()).Elem() convertedValue := performConversion(sourceMap.MapIndex(key).Interface(), newValue) if convertedValue != nil { destinationMap.SetMapIndex(newKey, *convertedValue) } else { destinationMap.SetMapIndex(newKey, newValue) } } } func convertStructToMap(sourceStruct, destinationMap reflect.Value) { destinationType := destinationMap.Type() sourceType := sourceStruct.Type() fieldCount := sourceStruct.NumField() for i := 0; i < fieldCount; i++ { fieldInfo := sourceType.Field(i) if fieldInfo.Name[0] < 'A' || fieldInfo.Name[0] > 'Z' { continue } newKey := reflect.New(destinationType.Key()).Elem() convertKey := performConversion(cast.GetLowerName(fieldInfo.Name), newKey) if convertKey != nil { newKey = *convertKey } newValue := reflect.New(destinationType.Elem()).Elem() convertedValue := performConversion(sourceStruct.Field(i).Interface(), newValue) if convertedValue != nil { destinationMap.SetMapIndex(newKey, *convertedValue) } else { destinationMap.SetMapIndex(newKey, newValue) } } } func convertSliceToSlice(sourceSlice, destinationSlice reflect.Value) *reflect.Value { destinationType := destinationSlice.Type() sourceLen := sourceSlice.Len() for i := 0; i < sourceLen; i++ { newItem := reflect.New(destinationType.Elem()).Elem() convertedValue := performConversion(sourceSlice.Index(i).Interface(), newItem) if convertedValue != nil { destinationSlice = reflect.Append(destinationSlice, *convertedValue) } else { destinationSlice = reflect.Append(destinationSlice, newItem) } } return &destinationSlice } type StructInfo struct { Fields []reflect.StructField Values map[string]reflect.Value Methods []reflect.Method MethodValues map[string]reflect.Value } func FlatStruct(data any) *StructInfo { return FlattenStruct(data, true) } func FlatStructWithUnexported(data any) *StructInfo { return FlattenStruct(data, false) } func FlattenStruct(data any, exportOnly bool) *StructInfo { info := &StructInfo{ Fields: []reflect.StructField{}, Values: make(map[string]reflect.Value), Methods: []reflect.Method{}, MethodValues: make(map[string]reflect.Value), } reflectValue := reflect.ValueOf(data) for reflectValue.Kind() == reflect.Ptr { reflectValue = reflectValue.Elem() } // 收集方法 (仅当为指针类型时) if reflect.TypeOf(data).Kind() == reflect.Ptr { methodCount := reflectValue.NumMethod() for i := 0; i < methodCount; i++ { method := reflectValue.Type().Method(i) if !exportOnly || method.IsExported() { info.Methods = append(info.Methods, method) info.MethodValues[method.Name] = reflectValue.Method(i) } } } walkStruct(reflectValue, exportOnly, 0, func(field reflect.StructField, value reflect.Value) { info.Fields = append(info.Fields, field) info.Values[field.Name] = value }) return info } type fieldVisitor func(field reflect.StructField, value reflect.Value) func walkStruct(value reflect.Value, exportOnly bool, depth int, visitor fieldVisitor) { if depth > 10 { return } for value.Kind() == reflect.Ptr { value = value.Elem() } if value.Kind() != reflect.Struct { return } structType := value.Type() for i := 0; i < value.NumField(); i++ { field := structType.Field(i) if exportOnly && !field.IsExported() { continue } val := value.Field(i) if field.Anonymous { walkStruct(val, exportOnly, depth+1, visitor) } else { visitor(field, val) } } }