499 lines
14 KiB
Go
499 lines
14 KiB
Go
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.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
|
|
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
|
|
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)
|
|
}
|
|
}
|
|
}
|