406 lines
10 KiB
Go
406 lines
10 KiB
Go
|
|
package convert
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"reflect"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"apigo.cc/go/cast"
|
||
|
|
"gopkg.in/yaml.v3"
|
||
|
|
)
|
||
|
|
|
||
|
|
// To 将 from 中的数据深度映射到 to 中。
|
||
|
|
// 核心哲学:意图优先。根据 to 的类型推断用户的意图,并尽力转化数据。
|
||
|
|
func To(from, to any) {
|
||
|
|
r := convert(from, to)
|
||
|
|
if r != nil {
|
||
|
|
toValue := reflect.ValueOf(to)
|
||
|
|
var prevValue reflect.Value
|
||
|
|
for toValue.Kind() == reflect.Ptr {
|
||
|
|
prevValue = toValue
|
||
|
|
toValue = toValue.Elem()
|
||
|
|
}
|
||
|
|
if prevValue.IsValid() {
|
||
|
|
prevValue.Elem().Set(*r)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Convert 是 To 的别名,保持向前兼容。
|
||
|
|
func Convert(from, to any) { To(from, to) }
|
||
|
|
|
||
|
|
func convert(from, to any) *reflect.Value {
|
||
|
|
var fromValue reflect.Value
|
||
|
|
var toValue reflect.Value
|
||
|
|
|
||
|
|
if v, ok := from.(reflect.Value); ok {
|
||
|
|
fromValue = v
|
||
|
|
} else {
|
||
|
|
fromValue = reflect.ValueOf(from)
|
||
|
|
}
|
||
|
|
|
||
|
|
if v, ok := to.(reflect.Value); ok {
|
||
|
|
toValue = v
|
||
|
|
} else {
|
||
|
|
toValue = reflect.ValueOf(to)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 1. 初始化目标容器
|
||
|
|
fixNilValue(toValue)
|
||
|
|
|
||
|
|
// 2. 获取底层业务数据
|
||
|
|
rawFrom := cast.FinalValue(fromValue)
|
||
|
|
destValue := cast.RealValue(toValue)
|
||
|
|
|
||
|
|
if !destValue.IsValid() {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// 3. 处理 Unmarshaler 接口
|
||
|
|
if destValue.CanAddr() {
|
||
|
|
addr := destValue.Addr().Interface()
|
||
|
|
if um, ok := addr.(json.Unmarshaler); ok {
|
||
|
|
_ = um.UnmarshalJSON(cast.JsonBytes(rawFrom.Interface()))
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
if um, ok := addr.(yaml.Unmarshaler); ok {
|
||
|
|
_ = um.UnmarshalYAML(&yaml.Node{Value: cast.String(rawFrom.Interface())})
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 4. 核心转换逻辑
|
||
|
|
fromType := finalType(rawFrom)
|
||
|
|
destType := destValue.Type()
|
||
|
|
|
||
|
|
// 兼容 interface{} 目标
|
||
|
|
if destType.Kind() == reflect.Interface {
|
||
|
|
if destValue.CanSet() {
|
||
|
|
destValue.Set(reflect.ValueOf(rawFrom.Interface()))
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 极致去摩擦:如果目标是单值,但输入是切片,自动取第一个元素进行后续处理
|
||
|
|
effectiveFrom := rawFrom
|
||
|
|
if fromType.Kind() == reflect.Slice && rawFrom.Len() > 0 && destType.Kind() != reflect.Slice && destType.Kind() != reflect.Array {
|
||
|
|
effectiveFrom = cast.FinalValue(rawFrom.Index(0))
|
||
|
|
}
|
||
|
|
|
||
|
|
var newValue *reflect.Value
|
||
|
|
|
||
|
|
switch destType.Kind() {
|
||
|
|
case reflect.Bool:
|
||
|
|
setOrNew(destValue, reflect.ValueOf(cast.Bool(effectiveFrom.Interface())), &newValue)
|
||
|
|
case reflect.String:
|
||
|
|
setOrNew(destValue, reflect.ValueOf(cast.String(effectiveFrom.Interface())), &newValue)
|
||
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||
|
|
setOrNew(destValue, reflect.ValueOf(cast.Int64(effectiveFrom.Interface())).Convert(destType), &newValue)
|
||
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||
|
|
setOrNew(destValue, reflect.ValueOf(cast.Uint64(effectiveFrom.Interface())).Convert(destType), &newValue)
|
||
|
|
case reflect.Float32, reflect.Float64:
|
||
|
|
setOrNew(destValue, reflect.ValueOf(cast.Float64(effectiveFrom.Interface())).Convert(destType), &newValue)
|
||
|
|
|
||
|
|
case reflect.Slice:
|
||
|
|
if destType.Elem().Kind() == reflect.Uint8 {
|
||
|
|
setOrNew(destValue, reflect.ValueOf(cast.JsonBytes(rawFrom.Interface())), &newValue)
|
||
|
|
} else {
|
||
|
|
workFrom := rawFrom
|
||
|
|
if fromType.Kind() == reflect.String {
|
||
|
|
str := rawFrom.String()
|
||
|
|
if !strings.HasPrefix(str, "[") && strings.Contains(str, ",") {
|
||
|
|
workFrom = reflect.ValueOf(cast.SplitTrim(str, ","))
|
||
|
|
} else if !strings.HasPrefix(str, "[") {
|
||
|
|
tmp := reflect.MakeSlice(reflect.SliceOf(fromType), 1, 1)
|
||
|
|
tmp.Index(0).Set(rawFrom)
|
||
|
|
workFrom = tmp
|
||
|
|
} else {
|
||
|
|
var arr []any
|
||
|
|
cast.UnJson(str, &arr)
|
||
|
|
workFrom = reflect.ValueOf(arr)
|
||
|
|
}
|
||
|
|
} else if fromType.Kind() != reflect.Slice {
|
||
|
|
tmp := reflect.MakeSlice(reflect.SliceOf(fromType), 1, 1)
|
||
|
|
tmp.Index(0).Set(rawFrom)
|
||
|
|
workFrom = tmp
|
||
|
|
}
|
||
|
|
return convertSliceToSlice(workFrom, destValue)
|
||
|
|
}
|
||
|
|
|
||
|
|
case reflect.Struct:
|
||
|
|
switch effectiveFrom.Kind() {
|
||
|
|
case reflect.Map:
|
||
|
|
convertMapToStruct(effectiveFrom, destValue)
|
||
|
|
case reflect.Struct:
|
||
|
|
convertStructToStruct(effectiveFrom, destValue)
|
||
|
|
case reflect.String:
|
||
|
|
var m map[string]any
|
||
|
|
cast.UnJson(effectiveFrom.String(), &m)
|
||
|
|
convertMapToStruct(reflect.ValueOf(m), destValue)
|
||
|
|
}
|
||
|
|
|
||
|
|
case reflect.Map:
|
||
|
|
if destValue.IsNil() {
|
||
|
|
destValue = reflect.MakeMap(destType)
|
||
|
|
newValue = &destValue
|
||
|
|
}
|
||
|
|
switch rawFrom.Kind() {
|
||
|
|
case reflect.Map:
|
||
|
|
convertMapToMap(rawFrom, destValue)
|
||
|
|
case reflect.Struct:
|
||
|
|
convertStructToMap(rawFrom, destValue)
|
||
|
|
case reflect.String:
|
||
|
|
var m map[string]any
|
||
|
|
cast.UnJson(rawFrom.String(), &m)
|
||
|
|
convertMapToMap(reflect.ValueOf(m), destValue)
|
||
|
|
}
|
||
|
|
|
||
|
|
case reflect.Func:
|
||
|
|
if rawFrom.Kind() == reflect.Func {
|
||
|
|
destValue.Set(reflect.MakeFunc(destType, func(goArgs []reflect.Value) []reflect.Value {
|
||
|
|
ins := make([]reflect.Value, 0)
|
||
|
|
for i := 0; i < destType.NumIn(); i++ {
|
||
|
|
if i < rawFrom.Type().NumIn() {
|
||
|
|
argP := reflect.New(rawFrom.Type().In(i))
|
||
|
|
convert(goArgs[i].Interface(), argP)
|
||
|
|
ins = append(ins, argP.Elem())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
out := rawFrom.Call(ins)
|
||
|
|
outs := make([]reflect.Value, 0)
|
||
|
|
for i := 0; i < destType.NumOut(); i++ {
|
||
|
|
outP := reflect.New(destType.Out(i))
|
||
|
|
if i < len(out) {
|
||
|
|
convert(out[i].Interface(), outP)
|
||
|
|
}
|
||
|
|
outs = append(outs, outP.Elem())
|
||
|
|
}
|
||
|
|
return outs
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return newValue
|
||
|
|
}
|
||
|
|
|
||
|
|
func setOrNew(dest, val reflect.Value, newValue **reflect.Value) {
|
||
|
|
if dest.CanSet() {
|
||
|
|
dest.Set(val)
|
||
|
|
} else {
|
||
|
|
*newValue = &val
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func fixNilValue(v reflect.Value) {
|
||
|
|
t := v.Type()
|
||
|
|
for t.Kind() == reflect.Ptr {
|
||
|
|
if v.IsNil() {
|
||
|
|
v.Set(reflect.New(v.Type().Elem()))
|
||
|
|
}
|
||
|
|
v = v.Elem()
|
||
|
|
t = t.Elem()
|
||
|
|
}
|
||
|
|
if t.Kind() == reflect.Slice && v.IsNil() {
|
||
|
|
v.Set(reflect.MakeSlice(v.Type(), 0, 0))
|
||
|
|
}
|
||
|
|
if t.Kind() == reflect.Map && v.IsNil() {
|
||
|
|
v.Set(reflect.MakeMap(v.Type()))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func finalType(v reflect.Value) reflect.Type {
|
||
|
|
if !v.IsValid() {
|
||
|
|
return reflect.TypeOf(nil)
|
||
|
|
}
|
||
|
|
t := v.Type()
|
||
|
|
for t.Kind() == reflect.Ptr {
|
||
|
|
t = t.Elem()
|
||
|
|
}
|
||
|
|
return t
|
||
|
|
}
|
||
|
|
|
||
|
|
func normalizeKey(s string) string {
|
||
|
|
return strings.Map(func(r rune) rune {
|
||
|
|
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
||
|
|
return r
|
||
|
|
}
|
||
|
|
if r >= 'A' && r <= 'Z' {
|
||
|
|
return r + 32
|
||
|
|
}
|
||
|
|
return -1
|
||
|
|
}, s)
|
||
|
|
}
|
||
|
|
|
||
|
|
func convertMapToStruct(from, to reflect.Value) {
|
||
|
|
keys := from.MapKeys()
|
||
|
|
keyMap := make(map[string]*reflect.Value)
|
||
|
|
|
||
|
|
for i := range keys {
|
||
|
|
keyMap[normalizeKey(cast.String(keys[i].Interface()))] = &keys[i]
|
||
|
|
}
|
||
|
|
|
||
|
|
toType := to.Type()
|
||
|
|
for i := 0; i < toType.NumField(); i++ {
|
||
|
|
f := toType.Field(i)
|
||
|
|
if f.Anonymous {
|
||
|
|
convertMapToStruct(from, to.Field(i))
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if f.Name[0] < 'A' || f.Name[0] > 'Z' {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
k := keyMap[normalizeKey(f.Name)]
|
||
|
|
if k != nil {
|
||
|
|
val := from.MapIndex(*k)
|
||
|
|
if val.IsValid() {
|
||
|
|
if to.CanAddr() {
|
||
|
|
if m, ok := to.Addr().Type().MethodByName("Parse" + f.Name); ok {
|
||
|
|
argP := reflect.New(m.Type.In(1))
|
||
|
|
convert(val, argP)
|
||
|
|
out := m.Func.Call([]reflect.Value{to.Addr(), argP.Elem()})
|
||
|
|
to.Field(i).Set(out[0])
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
}
|
||
|
|
r := convert(val, to.Field(i))
|
||
|
|
if r != nil {
|
||
|
|
to.Field(i).Set(*r)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func convertStructToStruct(from, to reflect.Value) {
|
||
|
|
fromType := from.Type()
|
||
|
|
keyMap := make(map[string]int)
|
||
|
|
|
||
|
|
for i := 0; i < fromType.NumField(); i++ {
|
||
|
|
f := fromType.Field(i)
|
||
|
|
if f.Name[0] >= 'A' && f.Name[0] <= 'Z' {
|
||
|
|
keyMap[normalizeKey(f.Name)] = i + 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
toType := to.Type()
|
||
|
|
for i := 0; i < toType.NumField(); i++ {
|
||
|
|
f := toType.Field(i)
|
||
|
|
if f.Anonymous {
|
||
|
|
convertStructToStruct(from, to.Field(i))
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if f.Name[0] < 'A' || f.Name[0] > 'Z' {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
k := keyMap[normalizeKey(f.Name)]
|
||
|
|
if k != 0 {
|
||
|
|
r := convert(from.Field(k-1), to.Field(i))
|
||
|
|
if r != nil {
|
||
|
|
to.Field(i).Set(*r)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func convertMapToMap(from, to reflect.Value) {
|
||
|
|
toType := to.Type()
|
||
|
|
for _, k := range from.MapKeys() {
|
||
|
|
keyItem := reflect.New(toType.Key()).Elem()
|
||
|
|
convert(k, keyItem)
|
||
|
|
valueItem := reflect.New(toType.Elem()).Elem()
|
||
|
|
r := convert(from.MapIndex(k), valueItem)
|
||
|
|
if r != nil {
|
||
|
|
to.SetMapIndex(keyItem, *r)
|
||
|
|
} else {
|
||
|
|
to.SetMapIndex(keyItem, valueItem)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func convertStructToMap(from, to reflect.Value) {
|
||
|
|
toType := to.Type()
|
||
|
|
fromType := from.Type()
|
||
|
|
for i := 0; i < from.NumField(); i++ {
|
||
|
|
f := fromType.Field(i)
|
||
|
|
if f.Name[0] < 'A' || f.Name[0] > 'Z' {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
keyItem := reflect.New(toType.Key()).Elem()
|
||
|
|
convert(cast.GetLowerName(f.Name), keyItem)
|
||
|
|
valueItem := reflect.New(toType.Elem()).Elem()
|
||
|
|
r := convert(from.Field(i), valueItem)
|
||
|
|
if r != nil {
|
||
|
|
to.SetMapIndex(keyItem, *r)
|
||
|
|
} else {
|
||
|
|
to.SetMapIndex(keyItem, valueItem)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func convertSliceToSlice(from, to reflect.Value) *reflect.Value {
|
||
|
|
toType := to.Type()
|
||
|
|
for i := 0; i < from.Len(); i++ {
|
||
|
|
valueItem := reflect.New(toType.Elem()).Elem()
|
||
|
|
r := convert(from.Index(i), valueItem)
|
||
|
|
if r != nil {
|
||
|
|
to = reflect.Append(to, *r)
|
||
|
|
} else {
|
||
|
|
to = reflect.Append(to, valueItem)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return &to
|
||
|
|
}
|
||
|
|
|
||
|
|
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 flat(data, true) }
|
||
|
|
func FlatStructWithUnexported(data any) *StructInfo { return flat(data, false) }
|
||
|
|
|
||
|
|
func flat(data any, onlyExported bool) *StructInfo {
|
||
|
|
out := &StructInfo{
|
||
|
|
Fields: []reflect.StructField{},
|
||
|
|
Values: make(map[string]reflect.Value),
|
||
|
|
Methods: []reflect.Method{},
|
||
|
|
MethodValues: make(map[string]reflect.Value),
|
||
|
|
}
|
||
|
|
var v reflect.Value
|
||
|
|
if rv, ok := data.(reflect.Value); ok { v = rv } else { v = reflect.ValueOf(data) }
|
||
|
|
makeStructInfo(v, out, onlyExported)
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
func makeStructInfo(v reflect.Value, out *StructInfo, onlyExported bool) {
|
||
|
|
for v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Ptr { v = v.Elem() }
|
||
|
|
fv := v
|
||
|
|
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { fv = v.Elem() }
|
||
|
|
if fv.Kind() == reflect.Struct {
|
||
|
|
t := v.Type()
|
||
|
|
if v.Kind() == reflect.Ptr {
|
||
|
|
for i := 0; i < v.NumMethod(); i++ {
|
||
|
|
m := t.Method(i)
|
||
|
|
if onlyExported && !m.IsExported() { continue }
|
||
|
|
out.Methods = append(out.Methods, m)
|
||
|
|
out.MethodValues[m.Name] = v.Method(i)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ft := fv.Type()
|
||
|
|
for i := 0; i < ft.NumField(); i++ {
|
||
|
|
f := ft.Field(i)
|
||
|
|
if onlyExported && !f.IsExported() { continue }
|
||
|
|
if f.Anonymous {
|
||
|
|
makeStructInfo(fv.Field(i), out, onlyExported)
|
||
|
|
} else {
|
||
|
|
out.Fields = append(out.Fields, f)
|
||
|
|
out.Values[f.Name] = fv.Field(i)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|