struct slice map
This commit is contained in:
152
copier.go
152
copier.go
@@ -3,20 +3,21 @@ package copier
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Copy(src, dst any, opts ...option) error {
|
func Copy(dst, src any, opts ...option) error {
|
||||||
opt := getOpt(opts...)
|
opt := getOpt(opts...)
|
||||||
return copy(src, dst, opt)
|
return copy(dst, src, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copy(src, dst any, opt *options) error {
|
func copy(dst, src any, opt *options) error {
|
||||||
var (
|
var (
|
||||||
srcValue = indirect(reflect.ValueOf(dst))
|
srcValue = indirect(reflect.ValueOf(src))
|
||||||
dstValue = indirect(reflect.ValueOf(src))
|
dstValue = indirect(reflect.ValueOf(dst))
|
||||||
)
|
)
|
||||||
|
|
||||||
if !dstValue.IsValid() {
|
if !srcValue.IsValid() {
|
||||||
return ErrInvalidCopyFrom
|
return ErrInvalidCopyFrom
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,60 +25,94 @@ func copy(src, dst any, opt *options) error {
|
|||||||
return ErrInvalidCopyDestination
|
return ErrInvalidCopyDestination
|
||||||
}
|
}
|
||||||
|
|
||||||
return deepCopy(srcValue, dstValue, 0, opt)
|
return deepCopy(dstValue, srcValue, 0, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
|
func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
|
||||||
switch dst.Kind() {
|
if src.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.ExceedMaxDepth(depth) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch src.Kind() {
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
// slice -> slice
|
// slice -> slice
|
||||||
return copySlice(src, dst, depth, opt)
|
return copySlice(dst, src, depth, opt)
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
switch src.Kind() {
|
switch dst.Kind() {
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
// map -> map
|
// map -> map
|
||||||
return copyMap(src, dst, depth, opt)
|
return copyMap(dst, src, depth, opt)
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
// struct -> map
|
// struct -> map
|
||||||
return copyStruct2Map(src, dst, depth, opt)
|
return copyStruct2Map(dst, src, depth, opt)
|
||||||
default:
|
default:
|
||||||
return ErrNotSupported
|
return ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
switch src.Kind() {
|
switch dst.Kind() {
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
// map -> struct
|
// map -> struct
|
||||||
return copyMap2Struct(src, dst, depth, opt)
|
return copyMap2Struct(dst, src, depth, opt)
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
// struct -> struct
|
// struct -> struct
|
||||||
return copyStruct(src, dst, depth, opt)
|
return copyStruct(dst, src, depth, opt)
|
||||||
default:
|
default:
|
||||||
return ErrNotSupported
|
return ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case reflect.Func:
|
||||||
|
if dst.Kind() == reflect.Func && dst.IsNil() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
case reflect.Pointer:
|
case reflect.Pointer:
|
||||||
if dst.IsNil() {
|
if dst.Kind() == reflect.Pointer && dst.IsNil() {
|
||||||
if !dst.CanSet() {
|
if !dst.CanSet() {
|
||||||
return ErrInvalidCopyDestination
|
return nil
|
||||||
}
|
}
|
||||||
p := reflect.New(dst.Type().Elem())
|
p := reflect.New(dst.Type().Elem())
|
||||||
dst.Set(p)
|
dst.Set(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
src = src.Elem()
|
if src.Kind() == reflect.Pointer {
|
||||||
dst = dst.Elem()
|
src = indirect(src)
|
||||||
return deepCopy(dst, src, depth, opt)
|
|
||||||
default:
|
|
||||||
return copyDefault(src, dst)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyStruct2Map(src, dst reflect.Value, depth int, opt *options) error {
|
if dst.Kind() == reflect.Pointer {
|
||||||
|
dst = dst.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
return deepCopy(dst, src, depth, opt)
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
if src.Kind() != dst.Kind() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyMap2Struct(src, dst reflect.Value, depth int, opt *options) error {
|
src = src.Elem()
|
||||||
|
newDst := reflect.New(src.Type().Elem())
|
||||||
|
|
||||||
|
if err := deepCopy(newDst, src, depth, opt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.Set(newDst)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return copyDefault(dst, src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyStruct2Map(dst, src reflect.Value, depth int, opt *options) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyMap2Struct(dst, src reflect.Value, depth int, opt *options) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,9 +121,26 @@ func copyMap(dst, src reflect.Value, depth int, opt *options) error {
|
|||||||
dst.Set(reflect.MakeMapWithSize(dst.Type(), src.Len()))
|
dst.Set(reflect.MakeMapWithSize(dst.Type(), src.Len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, key := range src.MapKeys() {
|
dstType, _ := indirectType(dst.Type())
|
||||||
value := src.MapIndex(key)
|
|
||||||
dst.SetMapIndex(key, value)
|
iter := src.MapRange()
|
||||||
|
for iter.Next() {
|
||||||
|
key := iter.Key()
|
||||||
|
value := iter.Value()
|
||||||
|
|
||||||
|
var copitedValue reflect.Value
|
||||||
|
switch dstType.Elem().Kind() {
|
||||||
|
case reflect.Interface:
|
||||||
|
copitedValue = reflect.New(value.Type()).Elem()
|
||||||
|
default:
|
||||||
|
copitedValue = reflect.New(dstType.Elem()).Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deepCopy(copitedValue, value, depth+1, opt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.SetMapIndex(key, copitedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -116,27 +168,48 @@ func copySlice(dst, src reflect.Value, depth int, opt *options) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func copyStruct(dst, src reflect.Value, depth int, opt *options) error {
|
func copyStruct(dst, src reflect.Value, depth int, opt *options) error {
|
||||||
typ := src.Type()
|
if dst.CanSet() {
|
||||||
for i := 0; i < typ.NumField(); i++ {
|
if _, ok := src.Interface().(time.Time); ok {
|
||||||
sf := typ.Field(i)
|
dst.Set(src)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dstValue := fieldByName(dst, sf.Name, opt)
|
typ := src.Type()
|
||||||
|
for i, n := 0, src.NumField(); i < n; i++ {
|
||||||
|
sf := typ.Field(i)
|
||||||
|
if sf.PkgPath != "" && !sf.Anonymous {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := parseTag(sf.Tag.Get(opt.tagName))
|
||||||
|
if tag.Contains(tagIgnore) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := toName(sf.Name, tag, opt)
|
||||||
|
|
||||||
|
dstValue := fieldByName(dst, name, opt) // fieldByName(dst, name, opt)
|
||||||
if !dstValue.IsValid() {
|
if !dstValue.IsValid() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deepCopy(dstValue, src.Field(i), depth+1, opt); err != nil {
|
sField := src.Field(i)
|
||||||
|
if opt.ignoreEmpty && sField.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deepCopy(dstValue, sField, depth+1, opt); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyDefault(dst, src reflect.Value) error {
|
func copyDefault(dst, src reflect.Value) error {
|
||||||
|
if src.Type().AssignableTo(dst.Type()) || src.Type().ConvertibleTo(dst.Type()) {
|
||||||
if src.Type().AssignableTo(dst.Type()) || dst.Type().ConvertibleTo(src.Type()) {
|
|
||||||
dst.Set(src.Convert(dst.Type()))
|
dst.Set(src.Convert(dst.Type()))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch dst.Kind() {
|
switch dst.Kind() {
|
||||||
@@ -145,6 +218,13 @@ func copyDefault(dst, src reflect.Value) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func toName(name string, tag *tagOption, opt *options) string {
|
||||||
|
if tag != nil && tag.Contains(tagToName) {
|
||||||
|
return tag.toname
|
||||||
|
}
|
||||||
|
|
||||||
|
return opt.NameConvert(name)
|
||||||
|
}
|
||||||
|
|
||||||
func fieldByName(v reflect.Value, name string, opt *options) reflect.Value {
|
func fieldByName(v reflect.Value, name string, opt *options) reflect.Value {
|
||||||
if opt.caseSensitive {
|
if opt.caseSensitive {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/charlienet/go-misc/json"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +21,34 @@ func TestCopyMap(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
dst := map[string]any{}
|
dst := map[string]*person{}
|
||||||
|
|
||||||
|
assert.NoError(t, Copy(&dst, src))
|
||||||
|
|
||||||
|
fmt.Println(src, dst)
|
||||||
|
|
||||||
|
fmt.Println(json.Struct2JsonIndent(dst))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiffStructMapCopy(t *testing.T) {
|
||||||
|
type person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
type user struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
src := map[string]*person{
|
||||||
|
"John": {
|
||||||
|
Name: "John",
|
||||||
|
Age: 30,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := map[string]*user{}
|
||||||
|
|
||||||
assert.NoError(t, Copy(&dst, src))
|
assert.NoError(t, Copy(&dst, src))
|
||||||
|
|
||||||
|
|||||||
223
copier_test.go
223
copier_test.go
@@ -1,2 +1,225 @@
|
|||||||
package copier
|
package copier
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Age int ``
|
||||||
|
Name string ``
|
||||||
|
Address Address ``
|
||||||
|
}
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
Country string
|
||||||
|
City string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Person2 struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopySameStruct(t *testing.T) {
|
||||||
|
src := &Person{
|
||||||
|
Name: "John",
|
||||||
|
Age: 30,
|
||||||
|
Address: Address{
|
||||||
|
Country: "USA",
|
||||||
|
City: "New York",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var dst Person
|
||||||
|
|
||||||
|
assert.NoError(t, Copy(&dst, src))
|
||||||
|
|
||||||
|
if dst.Name != src.Name || dst.Age != src.Age {
|
||||||
|
t.Errorf("Expected %v, got %v", src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("src,dst:", src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyDiffStruct(t *testing.T) {
|
||||||
|
src := &Person{
|
||||||
|
Name: "John",
|
||||||
|
Age: 30,
|
||||||
|
Address: Address{
|
||||||
|
Country: "USA",
|
||||||
|
City: "New York",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var dst Person2
|
||||||
|
|
||||||
|
assert.NoError(t, Copy(&dst, src))
|
||||||
|
t.Log("src,dst:", src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyPtrStruct(t *testing.T) {
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Address *Address
|
||||||
|
}
|
||||||
|
|
||||||
|
src := User{
|
||||||
|
Name: "John",
|
||||||
|
Age: 30,
|
||||||
|
Address: &Address{
|
||||||
|
Country: "USA",
|
||||||
|
City: "New York",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var dst User
|
||||||
|
|
||||||
|
assert.NoError(t, Copy(&dst, src))
|
||||||
|
assert.Equal(t, src, dst)
|
||||||
|
t.Log("src,dst:", src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymousFields(t *testing.T) {
|
||||||
|
t.Run("Should work with unexported ptr fields", func(t *testing.T) {
|
||||||
|
type nested struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type parentA struct {
|
||||||
|
*nested
|
||||||
|
}
|
||||||
|
type parentB struct {
|
||||||
|
*nested
|
||||||
|
}
|
||||||
|
|
||||||
|
from := parentA{nested: &nested{A: "a"}}
|
||||||
|
to := parentB{}
|
||||||
|
|
||||||
|
err := Copy(&to, &from)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
from.nested.A = "b"
|
||||||
|
|
||||||
|
if to.nested != nil {
|
||||||
|
t.Errorf("should be nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should work with unexported fields", func(t *testing.T) {
|
||||||
|
type nested struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type parentA struct {
|
||||||
|
nested
|
||||||
|
}
|
||||||
|
type parentB struct {
|
||||||
|
nested
|
||||||
|
}
|
||||||
|
|
||||||
|
from := parentA{nested: nested{A: "a"}}
|
||||||
|
to := parentB{}
|
||||||
|
|
||||||
|
err := Copy(&to, &from)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
from.nested.A = "b"
|
||||||
|
|
||||||
|
if to.nested.A == from.nested.A {
|
||||||
|
t.Errorf("should be different")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should work with exported ptr fields", func(t *testing.T) {
|
||||||
|
type Nested struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type parentA struct {
|
||||||
|
*Nested
|
||||||
|
}
|
||||||
|
type parentB struct {
|
||||||
|
*Nested
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldValue := "a"
|
||||||
|
from := parentA{Nested: &Nested{A: fieldValue}}
|
||||||
|
to := parentB{}
|
||||||
|
|
||||||
|
err := Copy(&to, &from)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
from.Nested.A = "b"
|
||||||
|
|
||||||
|
if to.Nested.A != fieldValue {
|
||||||
|
t.Errorf("should not change")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should work with exported ptr fields with same name src field", func(t *testing.T) {
|
||||||
|
type Nested struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type parentA struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type parentB struct {
|
||||||
|
*Nested
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldValue := "a"
|
||||||
|
from := parentA{A: fieldValue}
|
||||||
|
to := parentB{}
|
||||||
|
|
||||||
|
err := Copy(&to, &from)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
from.A = "b"
|
||||||
|
|
||||||
|
if to.Nested.A != fieldValue {
|
||||||
|
t.Errorf("should not change")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should work with exported fields", func(t *testing.T) {
|
||||||
|
type Nested struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type parentA struct {
|
||||||
|
Nested
|
||||||
|
}
|
||||||
|
type parentB struct {
|
||||||
|
Nested
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldValue := "a"
|
||||||
|
from := parentA{Nested: Nested{A: fieldValue}}
|
||||||
|
to := parentB{}
|
||||||
|
|
||||||
|
err := Copy(&to, &from)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
from.Nested.A = "b"
|
||||||
|
|
||||||
|
if to.Nested.A != fieldValue {
|
||||||
|
t.Errorf("should not change")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -2,10 +2,16 @@ module git.charlienet.top/go/copier
|
|||||||
|
|
||||||
go 1.25
|
go 1.25
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.11.1
|
require (
|
||||||
|
github.com/charlienet/go-misc v0.0.0-20250920151122-cb147afeabdf
|
||||||
|
github.com/stretchr/testify v1.11.1
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
13
go.sum
13
go.sum
@@ -1,7 +1,20 @@
|
|||||||
|
github.com/charlienet/go-misc v0.0.0-20250920151122-cb147afeabdf h1:AQS/jJma5vCm03dbHd2h8FcT/fWlhACpiqMWeuz3XgU=
|
||||||
|
github.com/charlienet/go-misc v0.0.0-20250920151122-cb147afeabdf/go.mod h1:+NpQcYzYcl/sLr+d04plyLWd2BS6dRmPoccyKn36Yl8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|||||||
23
optiongs.go
23
optiongs.go
@@ -12,7 +12,8 @@ type options struct {
|
|||||||
ignoreEmpty bool // 复制时忽略空字段
|
ignoreEmpty bool // 复制时忽略空字段
|
||||||
caseSensitive bool // 复制时大小写敏感
|
caseSensitive bool // 复制时大小写敏感
|
||||||
must bool // 只复制具有must标识的字段
|
must bool // 只复制具有must标识的字段
|
||||||
converters []TypeConverter // 类型转换器
|
convertersByName map[string]TypeConverter // 根据名称处理的类型转换器
|
||||||
|
converters []TypeConverter // 根据源和目标类型处理的类型转换器
|
||||||
fieldNameMapping map[string]string // 字段名转映射
|
fieldNameMapping map[string]string // 字段名转映射
|
||||||
nameConverter func(string) string // 字段名转换器
|
nameConverter func(string) string // 字段名转换器
|
||||||
}
|
}
|
||||||
@@ -32,7 +33,11 @@ type FieldNameConverter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getOpt(opts ...option) *options {
|
func getOpt(opts ...option) *options {
|
||||||
opt := DefaultOptions
|
opt := &options{
|
||||||
|
maxDepth: noDepthLimited,
|
||||||
|
tagName: defaultTag,
|
||||||
|
convertersByName: make(map[string]TypeConverter),
|
||||||
|
}
|
||||||
|
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(opt)
|
o(opt)
|
||||||
@@ -88,6 +93,15 @@ func WithMust() option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithTypeConvertByName(name string, f func(src any) (dst any, err error)) option {
|
||||||
|
return func(o *options) {
|
||||||
|
o.convertersByName[name] = TypeConverter{
|
||||||
|
FieldName: name,
|
||||||
|
Fn: f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithConverters(converters ...TypeConverter) option {
|
func WithConverters(converters ...TypeConverter) option {
|
||||||
return func(o *options) {
|
return func(o *options) {
|
||||||
o.converters = converters
|
o.converters = converters
|
||||||
@@ -111,8 +125,3 @@ func WithTagName(tagName string) option {
|
|||||||
o.tagName = tagName
|
o.tagName = tagName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultOptions = &options{
|
|
||||||
maxDepth: noDepthLimited,
|
|
||||||
tagName: defaultTag,
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user