convert_del/convert.go

499 lines
14 KiB
Go
Raw Normal View History

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.MustToJSONBytes(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.MustToJSONBytes(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
2026-05-01 21:04:08 +08:00
cast.UnmarshalJSON(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
2026-05-01 21:04:08 +08:00
cast.UnmarshalJSON(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
2026-05-01 21:04:08 +08:00
cast.UnmarshalJSON(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)
}
}
}