update
This commit is contained in:
263
copier.go
263
copier.go
@@ -6,16 +6,33 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/modern-go/reflect2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Copy(dst, src any, opts ...option) error {
|
type copier struct {
|
||||||
opt := getOpt(opts...)
|
opt *options
|
||||||
return copy(dst, src, opt)
|
visited map[reflect.Value]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func copy(dst, src any, opt *options) error {
|
func newCopier(opts ...option) *copier {
|
||||||
|
opt := getOpt(opts...)
|
||||||
|
var visited map[reflect.Value]struct{}
|
||||||
|
if opt.detectCircularRefs {
|
||||||
|
visited = make(map[reflect.Value]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &copier{
|
||||||
|
visited: visited,
|
||||||
|
opt: opt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *copier) Copy(dst, src any) error {
|
||||||
|
defer c.reset()
|
||||||
|
|
||||||
|
if dst == nil || src == nil {
|
||||||
|
return ErrInvalidCopyDestination
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
srcValue = indirect(reflect.ValueOf(src))
|
srcValue = indirect(reflect.ValueOf(src))
|
||||||
dstValue = indirect(reflect.ValueOf(dst))
|
dstValue = indirect(reflect.ValueOf(dst))
|
||||||
@@ -29,36 +46,46 @@ func copy(dst, src any, opt *options) error {
|
|||||||
return ErrInvalidCopyDestination
|
return ErrInvalidCopyDestination
|
||||||
}
|
}
|
||||||
|
|
||||||
return deepCopy(dstValue, srcValue, 0, "", opt)
|
return c.deepCopy(dstValue, srcValue, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deepCopy(dst, src reflect.Value, depth int, fieldName string, opt *options) error {
|
func (c *copier) reset() {
|
||||||
|
if c.opt.detectCircularRefs {
|
||||||
|
c.visited = make(map[reflect.Value]struct{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *copier) deepCopy(dst, src reflect.Value, depth int) error {
|
||||||
|
if c.ExceedMaxDepth(depth) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if src.IsZero() {
|
if src.IsZero() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.ExceedMaxDepth(depth) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fmt.Println("do deep copy src:", src.Kind().String(), "dst:", dst.Kind().String())
|
|
||||||
switch src.Kind() {
|
switch src.Kind() {
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
// slice -> slice
|
// slice -> slice
|
||||||
return copySlice(dst, src, depth, fieldName, opt)
|
switch dst.Kind() {
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
return c.copySlice(dst, src, depth)
|
||||||
|
default:
|
||||||
|
return ErrNotSupported
|
||||||
|
}
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
switch dst.Kind() {
|
switch dst.Kind() {
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
// map -> map
|
// map -> map
|
||||||
return copyMap(dst, src, depth, fieldName, opt)
|
return c.copyMap(dst, src, depth)
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
// map -> struct
|
// map -> struct
|
||||||
return copyMap2Struct(dst, src, depth, fieldName, opt)
|
return c.copyMap2Struct(dst, src, depth)
|
||||||
case reflect.Pointer:
|
case reflect.Pointer:
|
||||||
elemType := dst.Type().Elem()
|
elemType := dst.Type().Elem()
|
||||||
newPtr := reflect.New(elemType)
|
newPtr := reflect.New(elemType)
|
||||||
|
|
||||||
if err := deepCopy(newPtr.Elem(), src, depth+1, fieldName, opt); err != nil {
|
if err := c.deepCopy(newPtr.Elem(), src, depth+1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,17 +98,17 @@ func deepCopy(dst, src reflect.Value, depth int, fieldName string, opt *options)
|
|||||||
switch dst.Kind() {
|
switch dst.Kind() {
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
// struct -> map
|
// struct -> map
|
||||||
return copyStruct2Map(dst, src, depth, fieldName, opt)
|
return c.copyStruct2Map(dst, src, depth)
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
// struct -> struct
|
// struct -> struct
|
||||||
return copyStruct(dst, src, depth, opt)
|
return c.copyStruct(dst, src, depth)
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
if dst.IsNil() {
|
if dst.IsNil() {
|
||||||
dstType := dst.Type().Elem()
|
dstType := dst.Type().Elem()
|
||||||
newDst := reflect.MakeSlice(reflect.SliceOf(dstType), 1, 1)
|
newDst := reflect.MakeSlice(reflect.SliceOf(dstType), 1, 1)
|
||||||
dst.Set(newDst)
|
dst.Set(newDst)
|
||||||
|
|
||||||
if err := deepCopy(dst.Index(0), src, depth+1, fieldName, opt); err != nil {
|
if err := c.deepCopy(dst.Index(0), src, depth+1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +119,7 @@ func deepCopy(dst, src reflect.Value, depth int, fieldName string, opt *options)
|
|||||||
reflect.Copy(newSlice, dst)
|
reflect.Copy(newSlice, dst)
|
||||||
|
|
||||||
newDst := newSlice.Index(len)
|
newDst := newSlice.Index(len)
|
||||||
if err := deepCopy(newDst, src, depth+1, fieldName, opt); err != nil {
|
if err := c.deepCopy(newDst, src, depth+1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dst.Set(newSlice)
|
dst.Set(newSlice)
|
||||||
@@ -100,14 +127,9 @@ func deepCopy(dst, src reflect.Value, depth int, fieldName string, opt *options)
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return set(dst, src, fieldName, opt)
|
return c.set(dst, src)
|
||||||
}
|
}
|
||||||
|
|
||||||
case reflect.Func:
|
|
||||||
if dst.Kind() == reflect.Func && dst.IsNil() {
|
|
||||||
dst.Set(src)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case reflect.Pointer:
|
case reflect.Pointer:
|
||||||
if dst.Kind() == reflect.Pointer && dst.IsNil() {
|
if dst.Kind() == reflect.Pointer && dst.IsNil() {
|
||||||
if !dst.CanSet() {
|
if !dst.CanSet() {
|
||||||
@@ -125,7 +147,7 @@ func deepCopy(dst, src reflect.Value, depth int, fieldName string, opt *options)
|
|||||||
dst = dst.Elem()
|
dst = dst.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
return deepCopy(dst, src, depth, fieldName, opt)
|
return c.deepCopy(dst, src, depth)
|
||||||
|
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
if src.IsNil() {
|
if src.IsNil() {
|
||||||
@@ -133,26 +155,26 @@ func deepCopy(dst, src reflect.Value, depth int, fieldName string, opt *options)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if src.Kind() != dst.Kind() {
|
if src.Kind() != dst.Kind() {
|
||||||
return set(dst, src, fieldName, opt)
|
return c.set(dst, src)
|
||||||
}
|
}
|
||||||
|
|
||||||
src = src.Elem()
|
src = src.Elem()
|
||||||
newDst := reflect.New(src.Type().Elem())
|
newDst := reflect.New(src.Type().Elem())
|
||||||
|
|
||||||
if err := deepCopy(newDst, src, depth, fieldName, opt); err != nil {
|
if err := c.deepCopy(newDst, src, depth); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.Set(newDst)
|
dst.Set(newDst)
|
||||||
return nil
|
return nil
|
||||||
case reflect.Chan:
|
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
|
||||||
return ErrNotSupported
|
return ErrNotSupported
|
||||||
default:
|
default:
|
||||||
return set(dst, src, fieldName, opt)
|
return c.set(dst, src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyStruct2Map(dst, src reflect.Value, depth int, fieldName string, opt *options) error {
|
func (c *copier) copyStruct2Map(dst, src reflect.Value, depth int) error {
|
||||||
for i, n := 0, src.NumField(); i < n; i++ {
|
for i, n := 0, src.NumField(); i < n; i++ {
|
||||||
sf := src.Type().Field(i)
|
sf := src.Type().Field(i)
|
||||||
if sf.PkgPath != "" && !sf.Anonymous {
|
if sf.PkgPath != "" && !sf.Anonymous {
|
||||||
@@ -162,7 +184,7 @@ func copyStruct2Map(dst, src reflect.Value, depth int, fieldName string, opt *op
|
|||||||
if sf.Anonymous {
|
if sf.Anonymous {
|
||||||
switch sf.Type.Kind() {
|
switch sf.Type.Kind() {
|
||||||
case reflect.Struct, reflect.Pointer:
|
case reflect.Struct, reflect.Pointer:
|
||||||
if err := deepCopy(dst, src.Field(i), depth+1, fieldName, opt); err != nil {
|
if err := c.deepCopy(dst, src.Field(i), depth+1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,16 +192,16 @@ func copyStruct2Map(dst, src reflect.Value, depth int, fieldName string, opt *op
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tag := parseTag(sf.Tag.Get(opt.tagName))
|
tag := parseTag(sf.Tag.Get(c.opt.tagName))
|
||||||
if tag.Contains(tagIgnore) {
|
if tag.Contains(tagIgnore) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
name := getFieldName(sf.Name, tag, opt)
|
name := c.getFieldName(sf.Name, tag)
|
||||||
|
|
||||||
sField := src.Field(i)
|
sField := src.Field(i)
|
||||||
|
|
||||||
if opt.ignoreEmpty && sField.IsZero() {
|
if c.opt.ignoreEmpty && sField.IsZero() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +209,7 @@ func copyStruct2Map(dst, src reflect.Value, depth int, fieldName string, opt *op
|
|||||||
var newDst = reflect.ValueOf(make(map[string]any))
|
var newDst = reflect.ValueOf(make(map[string]any))
|
||||||
sField = indirect(sField)
|
sField = indirect(sField)
|
||||||
|
|
||||||
if err := deepCopy(newDst, sField, depth+1, name, opt); err != nil {
|
if err := c.deepCopy(newDst, sField, depth+1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,11 +222,9 @@ func copyStruct2Map(dst, src reflect.Value, depth int, fieldName string, opt *op
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyMap2Struct(dst, src reflect.Value, depth int, _ string, opt *options) error {
|
func (c *copier) copyMap2Struct(dst, src reflect.Value, depth int) error {
|
||||||
// 循环结构,然后从map取值
|
|
||||||
typ := dst.Type()
|
typ := dst.Type()
|
||||||
|
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||||
for i, n := 0, src.NumField(); i < n; i++ {
|
|
||||||
field := dst.Field(i)
|
field := dst.Field(i)
|
||||||
sf := typ.Field(i)
|
sf := typ.Field(i)
|
||||||
|
|
||||||
@@ -213,15 +233,12 @@ func copyMap2Struct(dst, src reflect.Value, depth int, _ string, opt *options) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sf.Anonymous {
|
if sf.Anonymous {
|
||||||
if err := deepCopy(field, src, depth+1, sf.Name, opt); err != nil {
|
if err := c.deepCopy(field, src, depth+1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
name := getFieldNameFromJsonTag(sf)
|
name := sf.Name
|
||||||
if name == "-" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mapValue := src.MapIndex(reflect.ValueOf(name))
|
mapValue := src.MapIndex(reflect.ValueOf(name))
|
||||||
if !mapValue.IsValid() {
|
if !mapValue.IsValid() {
|
||||||
@@ -233,11 +250,11 @@ func copyMap2Struct(dst, src reflect.Value, depth int, _ string, opt *options) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if mapValue.Kind() == reflect.Map || mapValue.Kind() == reflect.Array || mapValue.Kind() == reflect.Slice {
|
if mapValue.Kind() == reflect.Map || mapValue.Kind() == reflect.Array || mapValue.Kind() == reflect.Slice {
|
||||||
if err := deepCopy(field, mapValue, depth+1, name, opt); err != nil {
|
if err := c.deepCopy(field, mapValue, depth+1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := set(field, mapValue, name, opt); err != nil {
|
if err := c.set(field, mapValue); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +263,7 @@ func copyMap2Struct(dst, src reflect.Value, depth int, _ string, opt *options) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyMap(dst, src reflect.Value, depth int, fieldName string, opt *options) error {
|
func (c *copier) copyMap(dst, src reflect.Value, depth int) error {
|
||||||
if dst.IsNil() {
|
if dst.IsNil() {
|
||||||
dst.Set(reflect.MakeMapWithSize(dst.Type(), src.Len()))
|
dst.Set(reflect.MakeMapWithSize(dst.Type(), src.Len()))
|
||||||
}
|
}
|
||||||
@@ -259,11 +276,11 @@ func copyMap(dst, src reflect.Value, depth int, fieldName string, opt *options)
|
|||||||
value := iter.Value()
|
value := iter.Value()
|
||||||
|
|
||||||
if name, ok := key.Interface().(string); ok {
|
if name, ok := key.Interface().(string); ok {
|
||||||
fieldName = opt.NameConvert(name)
|
fieldName := c.getFieldName(name, nil)
|
||||||
key = reflect.ValueOf(fieldName)
|
key = reflect.ValueOf(fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.ignoreEmpty && value.IsZero() {
|
if c.opt.ignoreEmpty && value.IsZero() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,12 +294,12 @@ func copyMap(dst, src reflect.Value, depth int, fieldName string, opt *options)
|
|||||||
copitedValue = reflect.New(value.Type()).Elem()
|
copitedValue = reflect.New(value.Type()).Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
set(copitedValue, value, fieldName, opt)
|
c.set(copitedValue, value)
|
||||||
default:
|
default:
|
||||||
copitedValue = reflect.New(dstType.Elem()).Elem()
|
copitedValue = reflect.New(dstType.Elem()).Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deepCopy(copitedValue, value, depth+1, fieldName, opt); err != nil {
|
if err := c.deepCopy(copitedValue, value, depth+1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,28 +309,40 @@ func copyMap(dst, src reflect.Value, depth int, fieldName string, opt *options)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copySlice(dst, src reflect.Value, depth int, fieldName string, opt *options) error {
|
func (c *copier) copySlice(dst, src reflect.Value, depth int) error {
|
||||||
len := src.Len()
|
var len int
|
||||||
|
if src.Kind() == reflect.Struct {
|
||||||
|
len = 0
|
||||||
|
} else {
|
||||||
|
len = src.Len()
|
||||||
|
}
|
||||||
|
|
||||||
if dst.Len() > 0 && dst.Len() < src.Len() {
|
if dst.Len() > 0 && dst.Len() < src.Len() {
|
||||||
len = dst.Len()
|
len = dst.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
if dst.Kind() == reflect.Slice && dst.Len() == 0 && src.Len() > 0 {
|
if dst.Kind() == reflect.Slice && dst.Len() == 0 && len > 0 {
|
||||||
dstType := dst.Type().Elem()
|
dstType := dst.Type().Elem()
|
||||||
newDst := reflect.MakeSlice(reflect.SliceOf(dstType), len, len)
|
newDst := reflect.MakeSlice(reflect.SliceOf(dstType), len, len)
|
||||||
dst.Set(newDst)
|
dst.Set(newDst)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len; i++ {
|
if src.Kind() == reflect.Struct {
|
||||||
if err := deepCopy(dst.Index(i), src.Index(i), depth, fieldName, opt); err != nil {
|
if err := c.deepCopy(dst.Index(0), src, depth); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
if err := c.deepCopy(dst.Index(i), src.Index(i), depth); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyStruct(dst, src reflect.Value, depth int, opt *options) error {
|
func (c *copier) copyStruct(dst, src reflect.Value, depth int) error {
|
||||||
if dst.CanSet() {
|
if dst.CanSet() {
|
||||||
if _, ok := src.Interface().(time.Time); ok {
|
if _, ok := src.Interface().(time.Time); ok {
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
@@ -322,41 +351,42 @@ func copyStruct(dst, src reflect.Value, depth int, opt *options) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
typ := src.Type()
|
typ := src.Type()
|
||||||
fields := deepFields(typ)
|
fields := c.deepFields(typ)
|
||||||
for _, sf := range fields {
|
for _, sf := range fields {
|
||||||
tag := parseTag(sf.Tag.Get(opt.tagName))
|
name := c.getFieldName(sf.Field.Name, sf.Tag)
|
||||||
if tag.Contains(tagIgnore) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
name := getFieldName(sf.Name, tag, opt)
|
|
||||||
|
|
||||||
if nestedAnonymousField(dst, name) {
|
if nestedAnonymousField(dst, name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
typ := reflect2.TypeOf(dst)
|
dstValue := c.fieldByName(dst, name)
|
||||||
dstPtr := reflect2.PtrOf(dst)
|
|
||||||
typ.(reflect2.StructType).Field(1).UnsafeGet(dstPtr)
|
|
||||||
|
|
||||||
dstValue := fieldByName(dst, name, opt)
|
|
||||||
if !dstValue.IsValid() {
|
if !dstValue.IsValid() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sField := src.FieldByName(sf.Name)
|
if c.isCricularRefs(dstValue) {
|
||||||
if opt.ignoreEmpty && sField.IsZero() {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deepCopy(dstValue, sField, depth+1, name, opt); err != nil {
|
sField := src.FieldByName(sf.Field.Name)
|
||||||
|
if c.opt.ignoreEmpty && sField.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := c.lookupAndCopyWithConverter(dstValue, sField, name); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.deepCopy(dstValue, sField, depth+1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func set(dst, src reflect.Value, fieldName string, opt *options) error {
|
func (c *copier) set(dst, src reflect.Value) error {
|
||||||
if !src.IsValid() {
|
if !src.IsValid() {
|
||||||
return ErrInvalidCopyFrom
|
return ErrInvalidCopyFrom
|
||||||
}
|
}
|
||||||
@@ -365,11 +395,11 @@ func set(dst, src reflect.Value, fieldName string, opt *options) error {
|
|||||||
src = src.Elem()
|
src = src.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, err := lookupAndCopyWithConverter(dst, src, fieldName, opt); err != nil {
|
// if ok, err := c.lookupAndCopyWithConverter(dst, src, fieldName); err != nil {
|
||||||
return err
|
// return err
|
||||||
} else if ok {
|
// } else if ok {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
if dst.Kind() == reflect.Pointer {
|
if dst.Kind() == reflect.Pointer {
|
||||||
if dst.IsNil() {
|
if dst.IsNil() {
|
||||||
@@ -465,17 +495,23 @@ func set(dst, src reflect.Value, fieldName string, opt *options) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fieldsWrapper struct {
|
type fieldsWrapper struct {
|
||||||
Fields []reflect.StructField
|
Fields []fieldWrapper
|
||||||
once sync.Once
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fieldWrapper struct {
|
||||||
|
Field reflect.StructField
|
||||||
|
Tag *tagOption
|
||||||
|
Index int
|
||||||
|
}
|
||||||
|
|
||||||
var deepFieldsMap sync.Map
|
var deepFieldsMap sync.Map
|
||||||
|
|
||||||
func deepFields(reflectType reflect.Type) []reflect.StructField {
|
func (c *copier) deepFields(reflectType reflect.Type) []fieldWrapper {
|
||||||
if wrapper, ok := deepFieldsMap.Load(reflectType); ok {
|
if wrapper, ok := deepFieldsMap.Load(reflectType); ok {
|
||||||
w := wrapper.(*fieldsWrapper)
|
w := wrapper.(*fieldsWrapper)
|
||||||
w.once.Do(func() {
|
w.once.Do(func() {
|
||||||
w.Fields = calculateDeepFields(reflectType)
|
w.Fields = c.calculateDeepFields(reflectType)
|
||||||
})
|
})
|
||||||
return w.Fields
|
return w.Fields
|
||||||
}
|
}
|
||||||
@@ -484,7 +520,7 @@ func deepFields(reflectType reflect.Type) []reflect.StructField {
|
|||||||
w := wrapper.(*fieldsWrapper)
|
w := wrapper.(*fieldsWrapper)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
w.once.Do(func() {
|
w.once.Do(func() {
|
||||||
w.Fields = calculateDeepFields(reflectType)
|
w.Fields = c.calculateDeepFields(reflectType)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
w.once.Do(func() {})
|
w.once.Do(func() {})
|
||||||
@@ -493,10 +529,10 @@ func deepFields(reflectType reflect.Type) []reflect.StructField {
|
|||||||
return w.Fields
|
return w.Fields
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateDeepFields(reflectType reflect.Type) []reflect.StructField {
|
func (c *copier) calculateDeepFields(reflectType reflect.Type) []fieldWrapper {
|
||||||
reflectType, _ = indirectType(reflectType)
|
reflectType, _ = indirectType(reflectType)
|
||||||
num := reflectType.NumField()
|
num := reflectType.NumField()
|
||||||
res := make([]reflect.StructField, 0, num)
|
res := make([]fieldWrapper, 0, num)
|
||||||
if reflectType.Kind() == reflect.Struct {
|
if reflectType.Kind() == reflect.Struct {
|
||||||
for i := range num {
|
for i := range num {
|
||||||
sf := reflectType.Field(i)
|
sf := reflectType.Field(i)
|
||||||
@@ -504,11 +540,13 @@ func calculateDeepFields(reflectType reflect.Type) []reflect.StructField {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tag := parseTag(sf.Tag.Get(c.opt.tagName))
|
||||||
|
|
||||||
if sf.Anonymous {
|
if sf.Anonymous {
|
||||||
res = append(res, deepFields(sf.Type)...)
|
res = append(res, c.deepFields(sf.Type)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
res = append(res, sf)
|
res = append(res, fieldWrapper{Field: sf, Tag: tag, Index: i})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,18 +578,18 @@ func nestedAnonymousField(dst reflect.Value, fieldName string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupAndCopyWithConverter(dst, src reflect.Value, fieldName string, opt *options) (bool, error) {
|
func (c *copier) lookupAndCopyWithConverter(dst, src reflect.Value, fieldName string) (bool, error) {
|
||||||
if cnv, ok := opt.convertByName[fieldName]; ok {
|
if cnv, ok := c.opt.convertByName[fieldName]; ok {
|
||||||
return convert(dst, src, cnv)
|
return convert(dst, src, cnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opt.converters) > 0 {
|
if len(c.opt.converters) > 0 {
|
||||||
pair := converterPair{
|
pair := converterPair{
|
||||||
SrcType: src.Type(),
|
SrcType: src.Type(),
|
||||||
DstType: dst.Type(),
|
DstType: dst.Type(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if cnv, ok := opt.converters[pair]; ok {
|
if cnv, ok := c.opt.converters[pair]; ok {
|
||||||
return convert(dst, src, cnv)
|
return convert(dst, src, cnv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -575,36 +613,37 @@ func convert(dst, src reflect.Value, fn convertFunc) (bool, error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFieldNameFromJsonTag(field reflect.StructField) string {
|
func (c *copier) getFieldName(name string, tag *tagOption) string {
|
||||||
if tag := field.Tag.Get("json"); tag != "" {
|
|
||||||
if commaIndex := strings.Index(tag, ","); commaIndex != -1 {
|
|
||||||
name := tag[:commaIndex]
|
|
||||||
if name != "" {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
} else if tag != "" {
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return field.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFieldName(name string, tag *tagOption, opt *options) string {
|
|
||||||
if tag != nil && tag.Contains(tagToName) {
|
if tag != nil && tag.Contains(tagToName) {
|
||||||
return tag.toname
|
return tag.toname
|
||||||
}
|
}
|
||||||
|
|
||||||
return opt.NameConvert(name)
|
return c.opt.NameConvert(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fieldByName(v reflect.Value, name string, opt *options) reflect.Value {
|
func (c *copier) fieldByName(v reflect.Value, name string) reflect.Value {
|
||||||
if opt != nil && opt.caseSensitive {
|
if c.opt != nil && c.opt.caseSensitive {
|
||||||
return v.FieldByName(name)
|
return v.FieldByName(name)
|
||||||
}
|
}
|
||||||
return v.FieldByNameFunc(func(s string) bool { return strings.EqualFold(s, name) })
|
return v.FieldByNameFunc(func(s string) bool { return strings.EqualFold(s, name) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *copier) ExceedMaxDepth(depth int) bool {
|
||||||
|
return c.opt.maxDepth > 0 && depth >= c.opt.maxDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *copier) isCricularRefs(reflectValue reflect.Value) bool {
|
||||||
|
if c.opt.detectCircularRefs {
|
||||||
|
_, ok := c.visited[reflectValue.Addr()]
|
||||||
|
if !ok {
|
||||||
|
c.visited[reflectValue.Addr()] = struct{}{}
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func indirect(reflectValue reflect.Value) reflect.Value {
|
func indirect(reflectValue reflect.Value) reflect.Value {
|
||||||
for reflectValue.Kind() == reflect.Pointer {
|
for reflectValue.Kind() == reflect.Pointer {
|
||||||
reflectValue = reflectValue.Elem()
|
reflectValue = reflectValue.Elem()
|
||||||
|
|||||||
@@ -75,3 +75,42 @@ func TestAndSlice(t *testing.T) {
|
|||||||
t.Errorf("Expected %v, got %v", []string{"developer", "golang", "backend"}, dst)
|
t.Errorf("Expected %v, got %v", []string{"developer", "golang", "backend"}, dst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStructAppendSlice(t *testing.T) {
|
||||||
|
type person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
src := person{Name: "Alice", Age: 20}
|
||||||
|
|
||||||
|
dst := []person{
|
||||||
|
{Name: "Charlie", Age: 40},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Copy(&dst, src); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dst) != 2 {
|
||||||
|
t.Errorf("Expected %d elements, got %d", 2, len(dst))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendSlice(t *testing.T) {
|
||||||
|
src := []string{"developer", "golang", "backend"}
|
||||||
|
dst := []any{"do"}
|
||||||
|
|
||||||
|
if err := Copy(&dst, src); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(dst)
|
||||||
|
|
||||||
|
if !slices.Equal([]any{"do", "developer", "golang", "backend"}, dst) {
|
||||||
|
t.Errorf("Expected %v, got %v", []string{"do", "developer", "golang", "backend"}, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package copier
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -22,6 +23,12 @@ type Person2 struct {
|
|||||||
Age int
|
Age int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Employee struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Address *Address
|
||||||
|
}
|
||||||
|
|
||||||
func TestCopySameStruct(t *testing.T) {
|
func TestCopySameStruct(t *testing.T) {
|
||||||
src := &Person{
|
src := &Person{
|
||||||
Name: "John",
|
Name: "John",
|
||||||
@@ -282,3 +289,37 @@ func TestTypeConvert(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Log("src,dst:", src, dst)
|
t.Log("src,dst:", src, dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeepFileds(t *testing.T) {
|
||||||
|
var emp = Employee{}
|
||||||
|
typ := reflect.TypeOf(emp)
|
||||||
|
|
||||||
|
copier := newCopier()
|
||||||
|
wrapper := copier.deepFields(typ)
|
||||||
|
for _, v := range wrapper {
|
||||||
|
t.Log(v, v.Index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCopy(b *testing.B) {
|
||||||
|
var emp = Employee{
|
||||||
|
Name: "John",
|
||||||
|
Age: 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
for b.Loop() {
|
||||||
|
var dst Employee
|
||||||
|
Copy(&dst, &emp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDeepFields(b *testing.B) {
|
||||||
|
var emp = Employee{}
|
||||||
|
typ := reflect.TypeOf(emp)
|
||||||
|
|
||||||
|
for b.Loop() {
|
||||||
|
copier := newCopier()
|
||||||
|
copier.deepFields(typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
20
deepcopier.go
Normal file
20
deepcopier.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package copier
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
func Copy(dst, src any, opts ...option) error {
|
||||||
|
copier := newCopier(opts...)
|
||||||
|
return copier.Copy(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Clone(src any, opts ...option) (any, error) {
|
||||||
|
if src == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
srcType := reflect.TypeOf(src)
|
||||||
|
dst := reflect.New(srcType).Interface()
|
||||||
|
|
||||||
|
err := newCopier(opts...).Copy(dst, src)
|
||||||
|
return dst, err
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
@@ -1,3 +1,3 @@
|
|||||||
module git.charlienet.top/go/copier
|
module git.charlienet.top/go/copier
|
||||||
|
|
||||||
go 1.25
|
go 1.24
|
||||||
|
|||||||
53
optiongs.go
53
optiongs.go
@@ -9,15 +9,16 @@ const (
|
|||||||
type convertFunc func(any) (any, error)
|
type convertFunc func(any) (any, error)
|
||||||
|
|
||||||
type options struct {
|
type options struct {
|
||||||
tagName string // 标签名
|
tagName string // 标签名
|
||||||
maxDepth int // 最大复制深度
|
maxDepth int // 最大复制深度
|
||||||
ignoreEmpty bool // 复制时忽略空字段
|
ignoreEmpty bool // 复制时忽略空字段
|
||||||
caseSensitive bool // 复制时大小写敏感
|
caseSensitive bool // 复制时大小写敏感
|
||||||
must bool // 只复制具有must标识的字段
|
must bool // 只复制具有must标识的字段
|
||||||
fieldNameMapping map[string]string // 字段名转映射
|
detectCircularRefs bool // 检测循环引用
|
||||||
nameConverter func(string) string // 字段名转换函数
|
fieldNameMapping map[string]string // 字段名转映射
|
||||||
converters map[converterPair]convertFunc // 根据源和目标类型处理的类型转换器v
|
converters map[converterPair]convertFunc // 根据源和目标类型处理的类型转换器v
|
||||||
convertByName map[string]convertFunc // 根据名称处理的类型转换器
|
convertByName map[string]convertFunc // 根据名称处理的类型转换器
|
||||||
|
nameConverter func(string) string // 字段名转换函数
|
||||||
}
|
}
|
||||||
|
|
||||||
type option func(*options)
|
type option func(*options)
|
||||||
@@ -37,10 +38,8 @@ type converterPair struct {
|
|||||||
|
|
||||||
func getOpt(opts ...option) *options {
|
func getOpt(opts ...option) *options {
|
||||||
opt := &options{
|
opt := &options{
|
||||||
maxDepth: noDepthLimited,
|
maxDepth: noDepthLimited,
|
||||||
tagName: defaultTag,
|
tagName: defaultTag,
|
||||||
convertByName: make(map[string]convertFunc),
|
|
||||||
converters: make(map[converterPair]convertFunc),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
@@ -94,16 +93,44 @@ func WithMust() option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithDetectCircularRefs 添加检测循环引用标识
|
||||||
|
func WithDetectCircularRefs() option {
|
||||||
|
return func(o *options) {
|
||||||
|
o.detectCircularRefs = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithNameConvertByName 添加根据名称处理的类型转换器
|
// WithNameConvertByName 添加根据名称处理的类型转换器
|
||||||
func WithTypeConvertByName(name string, f func(src any) (dst any, err error)) option {
|
func WithTypeConvertByName(name string, f func(src any) (dst any, err error)) option {
|
||||||
return func(o *options) {
|
return func(o *options) {
|
||||||
|
if o.convertByName == nil {
|
||||||
|
o.convertByName = make(map[string]convertFunc)
|
||||||
|
}
|
||||||
|
|
||||||
o.convertByName[name] = f
|
o.convertByName[name] = f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithConvertByNames 添加根据名称处理的类型转换器
|
||||||
|
func WithTypeConvertByNames(nameConvert map[string]func(src any) (dst any, err error)) option {
|
||||||
|
return func(o *options) {
|
||||||
|
if o.convertByName == nil {
|
||||||
|
o.convertByName = make(map[string]convertFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, nameConvert := range nameConvert {
|
||||||
|
o.convertByName[name] = nameConvert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithTypeConvert 添加根据源和目标类型处理的类型转换器
|
// WithTypeConvert 添加根据源和目标类型处理的类型转换器
|
||||||
func WithConverters(converters ...TypeConverter) option {
|
func WithConverters(converters ...TypeConverter) option {
|
||||||
return func(o *options) {
|
return func(o *options) {
|
||||||
|
if len(converters) > 0 && o.converters == nil {
|
||||||
|
o.converters = make(map[converterPair]convertFunc)
|
||||||
|
}
|
||||||
|
|
||||||
for i := range converters {
|
for i := range converters {
|
||||||
pair := converterPair{
|
pair := converterPair{
|
||||||
SrcType: reflect.TypeOf(converters[i].SrcType),
|
SrcType: reflect.TypeOf(converters[i].SrcType),
|
||||||
|
|||||||
Reference in New Issue
Block a user