mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +08:00
refactor: 使用泛型重构参数绑定等
This commit is contained in:
4
server/pkg/cache/cache.go
vendored
4
server/pkg/cache/cache.go
vendored
@@ -1,8 +1,8 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/jsonx"
|
||||
"time"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
@@ -65,7 +65,7 @@ func (dc *defaultCache) GetInt(k string) (int, bool) {
|
||||
|
||||
func (dc *defaultCache) GetJson(k string, valPtr any) bool {
|
||||
if val, ok := dc.GetStr(k); ok {
|
||||
jsonx.To(val, valPtr)
|
||||
json.Unmarshal([]byte(val), valPtr)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/structx"
|
||||
)
|
||||
|
||||
@@ -27,13 +26,8 @@ func PageResultConv[F any, T any](pageResult *PageResult[F]) *PageResult[T] {
|
||||
if pageResult == nil {
|
||||
return NewEmptyPageResult[T]()
|
||||
}
|
||||
|
||||
return &PageResult[T]{
|
||||
Total: pageResult.Total,
|
||||
List: collx.ArrayMap(pageResult.List, func(item F) T {
|
||||
t := structx.NewInstance[T]()
|
||||
structx.Copy(t, item)
|
||||
return t
|
||||
}),
|
||||
List: structx.CopySliceTo[F, T](pageResult.List),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
)
|
||||
|
||||
// 绑定并校验请求结构体参数
|
||||
func BindJsonAndValid[T any](rc *Ctx, data T) T {
|
||||
func BindJsonAndValid[T any](rc *Ctx) T {
|
||||
data := structx.NewInstance[T]()
|
||||
if err := rc.BindJSON(data); err != nil {
|
||||
panic(ConvBindValidationError(data, err))
|
||||
} else {
|
||||
@@ -18,15 +19,15 @@ func BindJsonAndValid[T any](rc *Ctx, data T) T {
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定请求体中的json至form结构体,并拷贝至另一结构体
|
||||
func BindJsonAndCopyTo[T any](rc *Ctx, form any, toStruct T) T {
|
||||
BindJsonAndValid(rc, form)
|
||||
structx.Copy(toStruct, form)
|
||||
return toStruct
|
||||
// 绑定请求体中的json至form结构体,并拷贝至指定结构体
|
||||
func BindJsonAndCopyTo[F, T any](rc *Ctx) (F, T) {
|
||||
f := BindJsonAndValid[F](rc)
|
||||
return f, structx.CopyTo[T](f)
|
||||
}
|
||||
|
||||
// 绑定查询字符串到指定结构体
|
||||
func BindQuery[T any](rc *Ctx, data T) T {
|
||||
func BindQuery[T any](rc *Ctx) T {
|
||||
data := structx.NewInstance[T]()
|
||||
if err := rc.BindQuery(data); err != nil {
|
||||
panic(ConvBindValidationError(data, err))
|
||||
} else {
|
||||
@@ -35,7 +36,8 @@ func BindQuery[T any](rc *Ctx, data T) T {
|
||||
}
|
||||
|
||||
// 绑定查询字符串到指定结构体,并将分页信息也返回
|
||||
func BindQueryAndPage[T any](rc *Ctx, data T) (T, model.PageParam) {
|
||||
func BindQueryAndPage[T any](rc *Ctx) (T, model.PageParam) {
|
||||
data := structx.NewInstance[T]()
|
||||
if err := rc.BindQuery(data); err != nil {
|
||||
panic(ConvBindValidationError(data, err))
|
||||
} else {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/structx"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
@@ -16,9 +17,10 @@ func ToMap(jsonStr string) (collx.M, error) {
|
||||
return ToMapByBytes([]byte(jsonStr))
|
||||
}
|
||||
|
||||
// json字符串转结构体
|
||||
func To[T any](jsonStr string, res T) (T, error) {
|
||||
return res, json.Unmarshal([]byte(jsonStr), &res)
|
||||
// json字符串转结构体, T需为指针类型
|
||||
func To[T any](jsonStr string) (T, error) {
|
||||
res := structx.NewInstance[T]()
|
||||
return res, json.Unmarshal([]byte(jsonStr), res)
|
||||
}
|
||||
|
||||
// json字节数组转map
|
||||
|
||||
874
server/pkg/utils/structx/copier.go
Normal file
874
server/pkg/utils/structx/copier.go
Normal file
@@ -0,0 +1,874 @@
|
||||
package structx
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// github.com/jinzhu/copier
|
||||
|
||||
var (
|
||||
ErrInvalidCopyDestination = errors.New("copy destination must be non-nil and addressable")
|
||||
ErrInvalidCopyFrom = errors.New("copy from must be non-nil and addressable")
|
||||
ErrMapKeyNotMatch = errors.New("map's key type doesn't match")
|
||||
ErrNotSupported = errors.New("not supported")
|
||||
ErrFieldNameTagStartNotUpperCase = errors.New("copier field name tag must be start upper case")
|
||||
)
|
||||
|
||||
// These flags define options for tag handling
|
||||
const (
|
||||
// Denotes that a destination field must be copied to. If copying fails then a panic will ensue.
|
||||
tagMust uint8 = 1 << iota
|
||||
|
||||
// Denotes that the program should not panic when the must flag is on and
|
||||
// value is not copied. The program will return an error instead.
|
||||
tagNoPanic
|
||||
|
||||
// Ignore a destination field from being copied to.
|
||||
tagIgnore
|
||||
|
||||
// Denotes the fact that the field should be overridden, no matter if the IgnoreEmpty is set
|
||||
tagOverride
|
||||
|
||||
// Denotes that the value as been copied
|
||||
hasCopied
|
||||
|
||||
// Some default converter types for a nicer syntax
|
||||
String string = ""
|
||||
Bool bool = false
|
||||
Int int = 0
|
||||
Float32 float32 = 0
|
||||
Float64 float64 = 0
|
||||
)
|
||||
|
||||
// Option sets copy options
|
||||
type Option struct {
|
||||
// setting this value to true will ignore copying zero values of all the fields, including bools, as well as a
|
||||
// struct having all it's fields set to their zero values respectively (see IsZero() in reflect/value.go)
|
||||
IgnoreEmpty bool
|
||||
CaseSensitive bool
|
||||
DeepCopy bool
|
||||
Converters []TypeConverter
|
||||
// Custom field name mappings to copy values with different names in `fromValue` and `toValue` types.
|
||||
// Examples can be found in `copier_field_name_mapping_test.go`.
|
||||
FieldNameMapping []FieldNameMapping
|
||||
}
|
||||
|
||||
func (opt Option) converters() map[converterPair]TypeConverter {
|
||||
var converters = map[converterPair]TypeConverter{}
|
||||
|
||||
// save converters into map for faster lookup
|
||||
for i := range opt.Converters {
|
||||
pair := converterPair{
|
||||
SrcType: reflect.TypeOf(opt.Converters[i].SrcType),
|
||||
DstType: reflect.TypeOf(opt.Converters[i].DstType),
|
||||
}
|
||||
|
||||
converters[pair] = opt.Converters[i]
|
||||
}
|
||||
|
||||
return converters
|
||||
}
|
||||
|
||||
type TypeConverter struct {
|
||||
SrcType interface{}
|
||||
DstType interface{}
|
||||
Fn func(src interface{}) (dst interface{}, err error)
|
||||
}
|
||||
|
||||
type converterPair struct {
|
||||
SrcType reflect.Type
|
||||
DstType reflect.Type
|
||||
}
|
||||
|
||||
func (opt Option) fieldNameMapping() map[converterPair]FieldNameMapping {
|
||||
var mapping = map[converterPair]FieldNameMapping{}
|
||||
|
||||
for i := range opt.FieldNameMapping {
|
||||
pair := converterPair{
|
||||
SrcType: reflect.TypeOf(opt.FieldNameMapping[i].SrcType),
|
||||
DstType: reflect.TypeOf(opt.FieldNameMapping[i].DstType),
|
||||
}
|
||||
|
||||
mapping[pair] = opt.FieldNameMapping[i]
|
||||
}
|
||||
|
||||
return mapping
|
||||
}
|
||||
|
||||
type FieldNameMapping struct {
|
||||
SrcType interface{}
|
||||
DstType interface{}
|
||||
Mapping map[string]string
|
||||
}
|
||||
|
||||
// Tag Flags
|
||||
type flags struct {
|
||||
BitFlags map[string]uint8
|
||||
SrcNames tagNameMapping
|
||||
DestNames tagNameMapping
|
||||
}
|
||||
|
||||
// Field Tag name mapping
|
||||
type tagNameMapping struct {
|
||||
FieldNameToTag map[string]string
|
||||
TagToFieldName map[string]string
|
||||
}
|
||||
|
||||
// Copy copy things
|
||||
func Copy(toValue interface{}, fromValue interface{}) (err error) {
|
||||
return copier(toValue, fromValue, Option{})
|
||||
}
|
||||
|
||||
// CopyWithOption copy with option
|
||||
func CopyWithOption(toValue interface{}, fromValue interface{}, opt Option) (err error) {
|
||||
return copier(toValue, fromValue, opt)
|
||||
}
|
||||
|
||||
func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) {
|
||||
var (
|
||||
isSlice bool
|
||||
amount = 1
|
||||
from = indirect(reflect.ValueOf(fromValue))
|
||||
to = indirect(reflect.ValueOf(toValue))
|
||||
converters = opt.converters()
|
||||
mappings = opt.fieldNameMapping()
|
||||
)
|
||||
|
||||
if !to.CanAddr() {
|
||||
return ErrInvalidCopyDestination
|
||||
}
|
||||
|
||||
// Return is from value is invalid
|
||||
if !from.IsValid() {
|
||||
return ErrInvalidCopyFrom
|
||||
}
|
||||
|
||||
fromType, isPtrFrom := indirectType(from.Type())
|
||||
toType, _ := indirectType(to.Type())
|
||||
|
||||
if fromType.Kind() == reflect.Interface {
|
||||
fromType = reflect.TypeOf(from.Interface())
|
||||
}
|
||||
|
||||
if toType.Kind() == reflect.Interface {
|
||||
toType, _ = indirectType(reflect.TypeOf(to.Interface()))
|
||||
oldTo := to
|
||||
to = reflect.New(reflect.TypeOf(to.Interface())).Elem()
|
||||
defer func() {
|
||||
oldTo.Set(to)
|
||||
}()
|
||||
}
|
||||
|
||||
// Just set it if possible to assign for normal types
|
||||
if from.Kind() != reflect.Slice && from.Kind() != reflect.Struct && from.Kind() != reflect.Map && (from.Type().AssignableTo(to.Type()) || from.Type().ConvertibleTo(to.Type())) {
|
||||
if !isPtrFrom || !opt.DeepCopy {
|
||||
to.Set(from.Convert(to.Type()))
|
||||
} else {
|
||||
fromCopy := reflect.New(from.Type())
|
||||
fromCopy.Set(from.Elem())
|
||||
to.Set(fromCopy.Convert(to.Type()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if from.Kind() != reflect.Slice && fromType.Kind() == reflect.Map && toType.Kind() == reflect.Map {
|
||||
if !fromType.Key().ConvertibleTo(toType.Key()) {
|
||||
return ErrMapKeyNotMatch
|
||||
}
|
||||
|
||||
if to.IsNil() {
|
||||
to.Set(reflect.MakeMapWithSize(toType, from.Len()))
|
||||
}
|
||||
|
||||
for _, k := range from.MapKeys() {
|
||||
toKey := indirect(reflect.New(toType.Key()))
|
||||
isSet, err := set(toKey, k, opt.DeepCopy, converters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isSet {
|
||||
return fmt.Errorf("%w map, old key: %v, new key: %v", ErrNotSupported, k.Type(), toType.Key())
|
||||
}
|
||||
|
||||
elemType := toType.Elem()
|
||||
if elemType.Kind() != reflect.Slice {
|
||||
elemType, _ = indirectType(elemType)
|
||||
}
|
||||
toValue := indirect(reflect.New(elemType))
|
||||
isSet, err = set(toValue, from.MapIndex(k), opt.DeepCopy, converters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isSet {
|
||||
if err = copier(toValue.Addr().Interface(), from.MapIndex(k).Interface(), opt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
if elemType == toType.Elem() {
|
||||
to.SetMapIndex(toKey, toValue)
|
||||
break
|
||||
}
|
||||
elemType = reflect.PointerTo(elemType)
|
||||
toValue = toValue.Addr()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if from.Kind() == reflect.Slice && to.Kind() == reflect.Slice {
|
||||
// Return directly if both slices are nil
|
||||
if from.IsNil() && to.IsNil() {
|
||||
return
|
||||
}
|
||||
if to.IsNil() {
|
||||
slice := reflect.MakeSlice(reflect.SliceOf(to.Type().Elem()), from.Len(), from.Cap())
|
||||
to.Set(slice)
|
||||
}
|
||||
if fromType.ConvertibleTo(toType) {
|
||||
for i := 0; i < from.Len(); i++ {
|
||||
if to.Len() < i+1 {
|
||||
to.Set(reflect.Append(to, reflect.New(to.Type().Elem()).Elem()))
|
||||
}
|
||||
isSet, err := set(to.Index(i), from.Index(i), opt.DeepCopy, converters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isSet {
|
||||
// ignore error while copy slice element
|
||||
err = copier(to.Index(i).Addr().Interface(), from.Index(i).Interface(), opt)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if to.Len() > from.Len() {
|
||||
to.SetLen(from.Len())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct {
|
||||
// skip not supported type
|
||||
return
|
||||
}
|
||||
|
||||
if len(converters) > 0 {
|
||||
if ok, e := set(to, from, opt.DeepCopy, converters); e == nil && ok {
|
||||
// converter supported
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if from.Kind() == reflect.Slice || to.Kind() == reflect.Slice {
|
||||
isSlice = true
|
||||
if from.Kind() == reflect.Slice {
|
||||
amount = from.Len()
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < amount; i++ {
|
||||
var dest, source reflect.Value
|
||||
|
||||
if isSlice {
|
||||
// source
|
||||
if from.Kind() == reflect.Slice {
|
||||
source = indirect(from.Index(i))
|
||||
} else {
|
||||
source = indirect(from)
|
||||
}
|
||||
// dest
|
||||
dest = indirect(reflect.New(toType).Elem())
|
||||
} else {
|
||||
source = indirect(from)
|
||||
dest = indirect(to)
|
||||
}
|
||||
|
||||
if len(converters) > 0 {
|
||||
if ok, e := set(dest, source, opt.DeepCopy, converters); e == nil && ok {
|
||||
if isSlice {
|
||||
// FIXME: maybe should check the other types?
|
||||
if to.Type().Elem().Kind() == reflect.Ptr {
|
||||
to.Index(i).Set(dest.Addr())
|
||||
} else {
|
||||
if to.Len() < i+1 {
|
||||
reflect.Append(to, dest)
|
||||
} else {
|
||||
to.Index(i).Set(dest)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
to.Set(dest)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
destKind := dest.Kind()
|
||||
initDest := false
|
||||
if destKind == reflect.Interface {
|
||||
initDest = true
|
||||
dest = indirect(reflect.New(toType))
|
||||
}
|
||||
|
||||
// Get tag options
|
||||
flgs, err := getFlags(dest, source, toType, fromType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check source
|
||||
if source.IsValid() {
|
||||
copyUnexportedStructFields(dest, source)
|
||||
|
||||
// Copy from source field to dest field or method
|
||||
fromTypeFields := deepFields(fromType)
|
||||
for _, field := range fromTypeFields {
|
||||
name := field.Name
|
||||
|
||||
// Get bit flags for field
|
||||
fieldFlags := flgs.BitFlags[name]
|
||||
|
||||
// Check if we should ignore copying
|
||||
if (fieldFlags & tagIgnore) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldNamesMapping := getFieldNamesMapping(mappings, fromType, toType)
|
||||
|
||||
srcFieldName, destFieldName := getFieldName(name, flgs, fieldNamesMapping)
|
||||
|
||||
if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, fieldFlags, opt.IgnoreEmpty) {
|
||||
// process for nested anonymous field
|
||||
destFieldNotSet := false
|
||||
if f, ok := dest.Type().FieldByName(destFieldName); ok {
|
||||
// only initialize parent embedded struct pointer in the path
|
||||
for idx := range f.Index[:len(f.Index)-1] {
|
||||
destField := dest.FieldByIndex(f.Index[:idx+1])
|
||||
|
||||
if destField.Kind() != reflect.Ptr {
|
||||
continue
|
||||
}
|
||||
|
||||
if !destField.IsNil() {
|
||||
continue
|
||||
}
|
||||
if !destField.CanSet() {
|
||||
destFieldNotSet = true
|
||||
break
|
||||
}
|
||||
|
||||
// destField is a nil pointer that can be set
|
||||
newValue := reflect.New(destField.Type().Elem())
|
||||
destField.Set(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
if destFieldNotSet {
|
||||
break
|
||||
}
|
||||
|
||||
toField := fieldByName(dest, destFieldName, opt.CaseSensitive)
|
||||
if toField.IsValid() {
|
||||
if toField.CanSet() {
|
||||
isSet, err := set(toField, fromField, opt.DeepCopy, converters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isSet {
|
||||
if err := copier(toField.Addr().Interface(), fromField.Interface(), opt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if fieldFlags != 0 {
|
||||
// Note that a copy was made
|
||||
flgs.BitFlags[name] = fieldFlags | hasCopied
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// try to set to method
|
||||
var toMethod reflect.Value
|
||||
if dest.CanAddr() {
|
||||
toMethod = dest.Addr().MethodByName(destFieldName)
|
||||
} else {
|
||||
toMethod = dest.MethodByName(destFieldName)
|
||||
}
|
||||
|
||||
if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) {
|
||||
toMethod.Call([]reflect.Value{fromField})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy from from method to dest field
|
||||
for _, field := range deepFields(toType) {
|
||||
name := field.Name
|
||||
srcFieldName, destFieldName := getFieldName(name, flgs, getFieldNamesMapping(mappings, fromType, toType))
|
||||
|
||||
var fromMethod reflect.Value
|
||||
if source.CanAddr() {
|
||||
fromMethod = source.Addr().MethodByName(srcFieldName)
|
||||
} else {
|
||||
fromMethod = source.MethodByName(srcFieldName)
|
||||
}
|
||||
|
||||
if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, flgs.BitFlags[name], opt.IgnoreEmpty) {
|
||||
if toField := fieldByName(dest, destFieldName, opt.CaseSensitive); toField.IsValid() && toField.CanSet() {
|
||||
values := fromMethod.Call([]reflect.Value{})
|
||||
if len(values) >= 1 {
|
||||
set(toField, values[0], opt.DeepCopy, converters)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isSlice && to.Kind() == reflect.Slice {
|
||||
if dest.Addr().Type().AssignableTo(to.Type().Elem()) {
|
||||
if to.Len() < i+1 {
|
||||
to.Set(reflect.Append(to, dest.Addr()))
|
||||
} else {
|
||||
isSet, err := set(to.Index(i), dest.Addr(), opt.DeepCopy, converters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isSet {
|
||||
// ignore error while copy slice element
|
||||
err = copier(to.Index(i).Addr().Interface(), dest.Addr().Interface(), opt)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if dest.Type().AssignableTo(to.Type().Elem()) {
|
||||
if to.Len() < i+1 {
|
||||
to.Set(reflect.Append(to, dest))
|
||||
} else {
|
||||
isSet, err := set(to.Index(i), dest, opt.DeepCopy, converters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isSet {
|
||||
// ignore error while copy slice element
|
||||
err = copier(to.Index(i).Addr().Interface(), dest.Interface(), opt)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if initDest {
|
||||
to.Set(dest)
|
||||
}
|
||||
|
||||
err = checkBitFlags(flgs.BitFlags)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getFieldNamesMapping(mappings map[converterPair]FieldNameMapping, fromType reflect.Type, toType reflect.Type) map[string]string {
|
||||
var fieldNamesMapping map[string]string
|
||||
|
||||
if len(mappings) > 0 {
|
||||
pair := converterPair{
|
||||
SrcType: fromType,
|
||||
DstType: toType,
|
||||
}
|
||||
if v, ok := mappings[pair]; ok {
|
||||
fieldNamesMapping = v.Mapping
|
||||
}
|
||||
}
|
||||
return fieldNamesMapping
|
||||
}
|
||||
|
||||
func fieldByNameOrZeroValue(source reflect.Value, fieldName string) (value reflect.Value) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
value = reflect.Value{}
|
||||
}
|
||||
}()
|
||||
|
||||
return source.FieldByName(fieldName)
|
||||
}
|
||||
|
||||
func copyUnexportedStructFields(to, from reflect.Value) {
|
||||
if from.Kind() != reflect.Struct || to.Kind() != reflect.Struct || !from.Type().AssignableTo(to.Type()) {
|
||||
return
|
||||
}
|
||||
|
||||
// create a shallow copy of 'to' to get all fields
|
||||
tmp := indirect(reflect.New(to.Type()))
|
||||
tmp.Set(from)
|
||||
|
||||
// revert exported fields
|
||||
for i := 0; i < to.NumField(); i++ {
|
||||
if tmp.Field(i).CanSet() {
|
||||
tmp.Field(i).Set(to.Field(i))
|
||||
}
|
||||
}
|
||||
to.Set(tmp)
|
||||
}
|
||||
|
||||
func shouldIgnore(v reflect.Value, bitFlags uint8, ignoreEmpty bool) bool {
|
||||
return ignoreEmpty && bitFlags&tagOverride == 0 && v.IsZero()
|
||||
}
|
||||
|
||||
var deepFieldsLock sync.RWMutex
|
||||
var deepFieldsMap = make(map[reflect.Type][]reflect.StructField)
|
||||
|
||||
func deepFields(reflectType reflect.Type) []reflect.StructField {
|
||||
deepFieldsLock.RLock()
|
||||
cache, ok := deepFieldsMap[reflectType]
|
||||
deepFieldsLock.RUnlock()
|
||||
if ok {
|
||||
return cache
|
||||
}
|
||||
var res []reflect.StructField
|
||||
if reflectType, _ = indirectType(reflectType); reflectType.Kind() == reflect.Struct {
|
||||
fields := make([]reflect.StructField, 0, reflectType.NumField())
|
||||
|
||||
for i := 0; i < reflectType.NumField(); i++ {
|
||||
v := reflectType.Field(i)
|
||||
// PkgPath is the package path that qualifies a lower case (unexported)
|
||||
// field name. It is empty for upper case (exported) field names.
|
||||
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
|
||||
if v.PkgPath == "" {
|
||||
fields = append(fields, v)
|
||||
if v.Anonymous {
|
||||
// also consider fields of anonymous fields as fields of the root
|
||||
fields = append(fields, deepFields(v.Type)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
res = fields
|
||||
}
|
||||
|
||||
deepFieldsLock.Lock()
|
||||
deepFieldsMap[reflectType] = res
|
||||
deepFieldsLock.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
func indirect(reflectValue reflect.Value) reflect.Value {
|
||||
for reflectValue.Kind() == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
}
|
||||
return reflectValue
|
||||
}
|
||||
|
||||
func indirectType(reflectType reflect.Type) (_ reflect.Type, isPtr bool) {
|
||||
for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice {
|
||||
reflectType = reflectType.Elem()
|
||||
isPtr = true
|
||||
}
|
||||
return reflectType, isPtr
|
||||
}
|
||||
|
||||
func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]TypeConverter) (bool, error) {
|
||||
if !from.IsValid() {
|
||||
return true, nil
|
||||
}
|
||||
if ok, err := lookupAndCopyWithConverter(to, from, converters); err != nil {
|
||||
return false, err
|
||||
} else if ok {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if to.Kind() == reflect.Ptr {
|
||||
// set `to` to nil if from is nil
|
||||
if from.Kind() == reflect.Ptr && from.IsNil() {
|
||||
to.Set(reflect.Zero(to.Type()))
|
||||
return true, nil
|
||||
} else if to.IsNil() {
|
||||
// `from` -> `to`
|
||||
// sql.NullString -> *string
|
||||
if fromValuer, ok := driverValuer(from); ok {
|
||||
v, err := fromValuer.Value()
|
||||
if err != nil {
|
||||
return true, nil
|
||||
}
|
||||
// if `from` is not valid do nothing with `to`
|
||||
if v == nil {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
// allocate new `to` variable with default value (eg. *string -> new(string))
|
||||
to.Set(reflect.New(to.Type().Elem()))
|
||||
} else if from.Kind() != reflect.Ptr && from.IsZero() {
|
||||
to.Set(reflect.Zero(to.Type()))
|
||||
return true, nil
|
||||
}
|
||||
// depointer `to`
|
||||
to = to.Elem()
|
||||
}
|
||||
|
||||
if deepCopy {
|
||||
toKind := to.Kind()
|
||||
if toKind == reflect.Interface && to.IsNil() {
|
||||
if reflect.TypeOf(from.Interface()) != nil {
|
||||
to.Set(reflect.New(reflect.TypeOf(from.Interface())).Elem())
|
||||
toKind = reflect.TypeOf(to.Interface()).Kind()
|
||||
}
|
||||
}
|
||||
if from.Kind() == reflect.Ptr && from.IsNil() {
|
||||
to.Set(reflect.Zero(to.Type()))
|
||||
return true, nil
|
||||
}
|
||||
if _, ok := to.Addr().Interface().(sql.Scanner); !ok && (toKind == reflect.Struct || toKind == reflect.Map || toKind == reflect.Slice) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// try convert directly
|
||||
if from.Type().ConvertibleTo(to.Type()) {
|
||||
to.Set(from.Convert(to.Type()))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// try Scanner
|
||||
if toScanner, ok := to.Addr().Interface().(sql.Scanner); ok {
|
||||
// `from` -> `to`
|
||||
// *string -> sql.NullString
|
||||
if from.Kind() == reflect.Ptr {
|
||||
// if `from` is nil do nothing with `to`
|
||||
if from.IsNil() {
|
||||
return true, nil
|
||||
}
|
||||
// depointer `from`
|
||||
from = indirect(from)
|
||||
}
|
||||
// `from` -> `to`
|
||||
// string -> sql.NullString
|
||||
// set `to` by invoking method Scan(`from`)
|
||||
err := toScanner.Scan(from.Interface())
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// try Valuer
|
||||
if fromValuer, ok := driverValuer(from); ok {
|
||||
// `from` -> `to`
|
||||
// sql.NullString -> string
|
||||
v, err := fromValuer.Value()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
// if `from` is not valid do nothing with `to`
|
||||
if v == nil {
|
||||
return true, nil
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Type().AssignableTo(to.Type()) {
|
||||
to.Set(rv)
|
||||
return true, nil
|
||||
}
|
||||
if to.CanSet() && rv.Type().ConvertibleTo(to.Type()) {
|
||||
to.Set(rv.Convert(to.Type()))
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// from is ptr
|
||||
if from.Kind() == reflect.Ptr {
|
||||
return set(to, from.Elem(), deepCopy, converters)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// lookupAndCopyWithConverter looks up the type pair, on success the TypeConverter Fn func is called to copy src to dst field.
|
||||
func lookupAndCopyWithConverter(to, from reflect.Value, converters map[converterPair]TypeConverter) (copied bool, err error) {
|
||||
pair := converterPair{
|
||||
SrcType: from.Type(),
|
||||
DstType: to.Type(),
|
||||
}
|
||||
|
||||
if cnv, ok := converters[pair]; ok {
|
||||
result, err := cnv.Fn(from.Interface())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
to.Set(reflect.ValueOf(result))
|
||||
} else {
|
||||
// in case we've got a nil value to copy
|
||||
to.Set(reflect.Zero(to.Type()))
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// parseTags Parses struct tags and returns uint8 bit flags.
|
||||
func parseTags(tag string) (flg uint8, name string, err error) {
|
||||
for _, t := range strings.Split(tag, ",") {
|
||||
switch t {
|
||||
case "-":
|
||||
flg = tagIgnore
|
||||
return
|
||||
case "must":
|
||||
flg = flg | tagMust
|
||||
case "nopanic":
|
||||
flg = flg | tagNoPanic
|
||||
case "override":
|
||||
flg = flg | tagOverride
|
||||
default:
|
||||
if unicode.IsUpper([]rune(t)[0]) {
|
||||
name = strings.TrimSpace(t)
|
||||
} else {
|
||||
err = ErrFieldNameTagStartNotUpperCase
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getTagFlags Parses struct tags for bit flags, field name.
|
||||
func getFlags(dest, src reflect.Value, toType, fromType reflect.Type) (flags, error) {
|
||||
flgs := flags{
|
||||
BitFlags: map[string]uint8{},
|
||||
SrcNames: tagNameMapping{
|
||||
FieldNameToTag: map[string]string{},
|
||||
TagToFieldName: map[string]string{},
|
||||
},
|
||||
DestNames: tagNameMapping{
|
||||
FieldNameToTag: map[string]string{},
|
||||
TagToFieldName: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
var toTypeFields, fromTypeFields []reflect.StructField
|
||||
if dest.IsValid() {
|
||||
toTypeFields = deepFields(toType)
|
||||
}
|
||||
if src.IsValid() {
|
||||
fromTypeFields = deepFields(fromType)
|
||||
}
|
||||
|
||||
// Get a list dest of tags
|
||||
for _, field := range toTypeFields {
|
||||
tags := field.Tag.Get("copier")
|
||||
if tags != "" {
|
||||
var name string
|
||||
var err error
|
||||
if flgs.BitFlags[field.Name], name, err = parseTags(tags); err != nil {
|
||||
return flags{}, err
|
||||
} else if name != "" {
|
||||
flgs.DestNames.FieldNameToTag[field.Name] = name
|
||||
flgs.DestNames.TagToFieldName[name] = field.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list source of tags
|
||||
for _, field := range fromTypeFields {
|
||||
tags := field.Tag.Get("copier")
|
||||
if tags != "" {
|
||||
var name string
|
||||
var err error
|
||||
|
||||
if _, name, err = parseTags(tags); err != nil {
|
||||
return flags{}, err
|
||||
} else if name != "" {
|
||||
flgs.SrcNames.FieldNameToTag[field.Name] = name
|
||||
flgs.SrcNames.TagToFieldName[name] = field.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flgs, nil
|
||||
}
|
||||
|
||||
// checkBitFlags Checks flags for error or panic conditions.
|
||||
func checkBitFlags(flagsList map[string]uint8) (err error) {
|
||||
// Check flag conditions were met
|
||||
for name, flgs := range flagsList {
|
||||
if flgs&hasCopied == 0 {
|
||||
switch {
|
||||
case flgs&tagMust != 0 && flgs&tagNoPanic != 0:
|
||||
err = fmt.Errorf("field %s has must tag but was not copied", name)
|
||||
return
|
||||
case flgs&(tagMust) != 0:
|
||||
panic(fmt.Sprintf("Field %s has must tag but was not copied", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getFieldName(fieldName string, flgs flags, fieldNameMapping map[string]string) (srcFieldName string, destFieldName string) {
|
||||
// get dest field name
|
||||
if name, ok := fieldNameMapping[fieldName]; ok {
|
||||
srcFieldName = fieldName
|
||||
destFieldName = name
|
||||
return
|
||||
}
|
||||
|
||||
if srcTagName, ok := flgs.SrcNames.FieldNameToTag[fieldName]; ok {
|
||||
destFieldName = srcTagName
|
||||
if destTagName, ok := flgs.DestNames.TagToFieldName[srcTagName]; ok {
|
||||
destFieldName = destTagName
|
||||
}
|
||||
} else {
|
||||
if destTagName, ok := flgs.DestNames.TagToFieldName[fieldName]; ok {
|
||||
destFieldName = destTagName
|
||||
}
|
||||
}
|
||||
if destFieldName == "" {
|
||||
destFieldName = fieldName
|
||||
}
|
||||
|
||||
// get source field name
|
||||
if destTagName, ok := flgs.DestNames.FieldNameToTag[fieldName]; ok {
|
||||
srcFieldName = destTagName
|
||||
if srcField, ok := flgs.SrcNames.TagToFieldName[destTagName]; ok {
|
||||
srcFieldName = srcField
|
||||
}
|
||||
} else {
|
||||
if srcField, ok := flgs.SrcNames.TagToFieldName[fieldName]; ok {
|
||||
srcFieldName = srcField
|
||||
}
|
||||
}
|
||||
|
||||
if srcFieldName == "" {
|
||||
srcFieldName = fieldName
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) {
|
||||
if !v.CanAddr() {
|
||||
i, ok = v.Interface().(driver.Valuer)
|
||||
return
|
||||
}
|
||||
|
||||
i, ok = v.Addr().Interface().(driver.Valuer)
|
||||
return
|
||||
}
|
||||
|
||||
func fieldByName(v reflect.Value, name string, caseSensitive bool) reflect.Value {
|
||||
if caseSensitive {
|
||||
return v.FieldByName(name)
|
||||
}
|
||||
|
||||
return v.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) })
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package structx
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -11,126 +10,18 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Copy copy things,引用至copier
|
||||
func Copy(toValue any, fromValue any) (err error) {
|
||||
var (
|
||||
isSlice bool
|
||||
amount = 1
|
||||
from = Indirect(reflect.ValueOf(fromValue))
|
||||
to = Indirect(reflect.ValueOf(toValue))
|
||||
)
|
||||
// CopyTo 将fromValue转为T类型并返回
|
||||
func CopyTo[T any](fromValue any) T {
|
||||
t := NewInstance[T]()
|
||||
Copy(t, fromValue)
|
||||
return t
|
||||
}
|
||||
|
||||
if !to.CanAddr() {
|
||||
return errors.New("copy to value is unaddressable")
|
||||
}
|
||||
|
||||
// Return is from value is invalid
|
||||
if !from.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
fromType := IndirectType(from.Type())
|
||||
toType := IndirectType(to.Type())
|
||||
|
||||
// Just set it if possible to assign
|
||||
// And need to do copy anyway if the type is struct
|
||||
if fromType.Kind() != reflect.Struct && from.Type().AssignableTo(to.Type()) {
|
||||
to.Set(from)
|
||||
return
|
||||
}
|
||||
|
||||
if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
|
||||
if to.Kind() == reflect.Slice {
|
||||
isSlice = true
|
||||
if from.Kind() == reflect.Slice {
|
||||
amount = from.Len()
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < amount; i++ {
|
||||
var dest, source reflect.Value
|
||||
|
||||
if isSlice {
|
||||
// source
|
||||
if from.Kind() == reflect.Slice {
|
||||
source = Indirect(from.Index(i))
|
||||
} else {
|
||||
source = Indirect(from)
|
||||
}
|
||||
// dest
|
||||
dest = Indirect(reflect.New(toType).Elem())
|
||||
} else {
|
||||
source = Indirect(from)
|
||||
dest = Indirect(to)
|
||||
}
|
||||
|
||||
// check source
|
||||
if source.IsValid() {
|
||||
fromTypeFields := deepFields(fromType)
|
||||
//fmt.Printf("%#v", fromTypeFields)
|
||||
// Copy from field to field or method
|
||||
for _, field := range fromTypeFields {
|
||||
name := field.Name
|
||||
|
||||
if fromField := source.FieldByName(name); fromField.IsValid() {
|
||||
// has field
|
||||
if toField := dest.FieldByName(name); toField.IsValid() {
|
||||
if toField.CanSet() {
|
||||
if !set(toField, fromField) {
|
||||
if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// try to set to method
|
||||
var toMethod reflect.Value
|
||||
if dest.CanAddr() {
|
||||
toMethod = dest.Addr().MethodByName(name)
|
||||
} else {
|
||||
toMethod = dest.MethodByName(name)
|
||||
}
|
||||
|
||||
if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) {
|
||||
toMethod.Call([]reflect.Value{fromField})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy from method to field
|
||||
for _, field := range deepFields(toType) {
|
||||
name := field.Name
|
||||
|
||||
var fromMethod reflect.Value
|
||||
if source.CanAddr() {
|
||||
fromMethod = source.Addr().MethodByName(name)
|
||||
} else {
|
||||
fromMethod = source.MethodByName(name)
|
||||
}
|
||||
|
||||
if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 {
|
||||
if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() {
|
||||
values := fromMethod.Call([]reflect.Value{})
|
||||
if len(values) >= 1 {
|
||||
set(toField, values[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if isSlice {
|
||||
if dest.Addr().Type().AssignableTo(to.Type().Elem()) {
|
||||
to.Set(reflect.Append(to, dest.Addr()))
|
||||
} else if dest.Type().AssignableTo(to.Type().Elem()) {
|
||||
to.Set(reflect.Append(to, dest))
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
// CopySliceTo 将fromValue转为[]T类型并返回
|
||||
func CopySliceTo[F, T any](fromValue []F) []T {
|
||||
var to []T
|
||||
Copy(&to, fromValue)
|
||||
return to
|
||||
}
|
||||
|
||||
// 对结构体的每个字段以及字段值执行doWith回调函数, 包括匿名属性的字段
|
||||
@@ -158,23 +49,6 @@ func DoWithFields(str any, doWith func(fType reflect.StructField, fValue reflect
|
||||
return nil
|
||||
}
|
||||
|
||||
func deepFields(reflectType reflect.Type) []reflect.StructField {
|
||||
var fields []reflect.StructField
|
||||
|
||||
if reflectType = IndirectType(reflectType); reflectType.Kind() == reflect.Struct {
|
||||
for i := 0; i < reflectType.NumField(); i++ {
|
||||
v := reflectType.Field(i)
|
||||
if v.Anonymous {
|
||||
fields = append(fields, deepFields(v.Type)...)
|
||||
} else {
|
||||
fields = append(fields, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func Indirect(reflectValue reflect.Value) reflect.Value {
|
||||
for reflectValue.Kind() == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
@@ -189,35 +63,6 @@ func IndirectType(reflectType reflect.Type) reflect.Type {
|
||||
return reflectType
|
||||
}
|
||||
|
||||
func set(to, from reflect.Value) bool {
|
||||
if from.IsValid() {
|
||||
if to.Kind() == reflect.Ptr {
|
||||
//set `to` to nil if from is nil
|
||||
if from.Kind() == reflect.Ptr && from.IsNil() {
|
||||
to.Set(reflect.Zero(to.Type()))
|
||||
return true
|
||||
} else if to.IsNil() {
|
||||
to.Set(reflect.New(to.Type().Elem()))
|
||||
}
|
||||
to = to.Elem()
|
||||
}
|
||||
|
||||
if from.Type().ConvertibleTo(to.Type()) {
|
||||
to.Set(from.Convert(to.Type()))
|
||||
} else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok {
|
||||
err := scanner.Scan(from.Interface())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
} else if from.Kind() == reflect.Ptr {
|
||||
return set(to, from.Elem())
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Map2Struct(m map[string]any, s any) error {
|
||||
toValue := Indirect(reflect.ValueOf(s))
|
||||
if !toValue.CanAddr() {
|
||||
|
||||
Reference in New Issue
Block a user