convert/convert.go

406 lines
10 KiB
Go
Raw Normal View History

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)
}
}
}
}