type convert

This commit is contained in:
2025-09-24 15:04:56 +08:00
parent 442cff29ff
commit 71b893cc54
4 changed files with 197 additions and 69 deletions

106
copier.go
View File

@@ -26,10 +26,10 @@ func copy(dst, src any, opt *options) error {
return ErrInvalidCopyDestination return ErrInvalidCopyDestination
} }
return deepCopy(dstValue, srcValue, 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, fieldName string, opt *options) error {
if src.IsZero() { if src.IsZero() {
return nil return nil
} }
@@ -42,20 +42,20 @@ func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
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, opt) return copySlice(dst, src, depth, fieldName, opt)
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, opt) return copyMap(dst, src, depth, fieldName, opt)
case reflect.Struct: case reflect.Struct:
// map -> struct // map -> struct
return copyMap2Struct(dst, src, depth, opt) return copyMap2Struct(dst, src, depth, fieldName, opt)
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, opt); err != nil { if err := deepCopy(newPtr.Elem(), src, depth+1, fieldName, opt); err != nil {
return err return err
} }
@@ -68,12 +68,12 @@ func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
switch dst.Kind() { switch dst.Kind() {
case reflect.Map: case reflect.Map:
// struct -> map // struct -> map
return copyStruct2Map(dst, src, depth, opt) return copyStruct2Map(dst, src, depth, fieldName, opt)
case reflect.Struct: case reflect.Struct:
// struct -> struct // struct -> struct
return copyStruct(dst, src, depth, opt) return copyStruct(dst, src, depth, opt)
default: default:
return ErrNotSupported return set(dst, src, fieldName, opt)
} }
case reflect.Func: case reflect.Func:
@@ -98,7 +98,7 @@ func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
dst = dst.Elem() dst = dst.Elem()
} }
return deepCopy(dst, src, depth, opt) return deepCopy(dst, src, depth, fieldName, opt)
case reflect.Interface: case reflect.Interface:
if src.IsNil() { if src.IsNil() {
@@ -106,13 +106,13 @@ func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
} }
if src.Kind() != dst.Kind() { if src.Kind() != dst.Kind() {
return set(dst, src) return set(dst, src, fieldName, opt)
} }
src = src.Elem() src = src.Elem()
newDst := reflect.New(src.Type().Elem()) newDst := reflect.New(src.Type().Elem())
if err := deepCopy(newDst, src, depth, opt); err != nil { if err := deepCopy(newDst, src, depth, fieldName, opt); err != nil {
return err return err
} }
@@ -121,11 +121,11 @@ func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
case reflect.Chan: case reflect.Chan:
return ErrNotSupported return ErrNotSupported
default: default:
return set(dst, src) return set(dst, src, fieldName, opt)
} }
} }
func copyStruct2Map(dst, src reflect.Value, depth int, opt *options) error { func copyStruct2Map(dst, src reflect.Value, depth int, fieldName string, opt *options) 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 {
@@ -135,7 +135,7 @@ func copyStruct2Map(dst, src reflect.Value, depth int, opt *options) error {
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, opt); err != nil { if err := deepCopy(dst, src.Field(i), depth+1, fieldName, opt); err != nil {
return err return err
} }
} }
@@ -160,7 +160,7 @@ func copyStruct2Map(dst, src reflect.Value, depth int, opt *options) error {
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, opt); err != nil { if err := deepCopy(newDst, sField, depth+1, name, opt); err != nil {
return err return err
} }
@@ -173,7 +173,7 @@ func copyStruct2Map(dst, src reflect.Value, depth int, opt *options) error {
return nil return nil
} }
func copyMap2Struct(dst, src reflect.Value, depth int, opt *options) error { func copyMap2Struct(dst, src reflect.Value, depth int, _ string, opt *options) error {
// 循环结构然后从map取值 // 循环结构然后从map取值
typ := dst.Type() typ := dst.Type()
for i := 0; i < dst.NumField(); i++ { for i := 0; i < dst.NumField(); i++ {
@@ -184,12 +184,12 @@ func copyMap2Struct(dst, src reflect.Value, depth int, opt *options) error {
continue continue
} }
fieldName := getFieldName(sf) name := getFieldName(sf)
if fieldName == "-" { if name == "-" {
continue continue
} }
mapValue := src.MapIndex(reflect.ValueOf(fieldName)) mapValue := src.MapIndex(reflect.ValueOf(name))
if !mapValue.IsValid() { if !mapValue.IsValid() {
continue continue
} }
@@ -199,11 +199,11 @@ func copyMap2Struct(dst, src reflect.Value, depth int, opt *options) error {
} }
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, opt); err != nil { if err := deepCopy(field, mapValue, depth+1, name, opt); err != nil {
return err return err
} }
} else { } else {
if err := set(field, mapValue); err != nil { if err := set(field, mapValue, name, opt); err != nil {
return err return err
} }
} }
@@ -212,7 +212,7 @@ func copyMap2Struct(dst, src reflect.Value, depth int, opt *options) error {
return nil return nil
} }
func copyMap(dst, src reflect.Value, depth int, opt *options) error { func copyMap(dst, src reflect.Value, depth int, fieldName string, opt *options) error {
if dst.IsNil() { if dst.IsNil() {
dst.Set(reflect.MakeMapWithSize(dst.Type(), src.Len())) dst.Set(reflect.MakeMapWithSize(dst.Type(), src.Len()))
} }
@@ -224,6 +224,15 @@ func copyMap(dst, src reflect.Value, depth int, opt *options) error {
key := iter.Key() key := iter.Key()
value := iter.Value() value := iter.Value()
if name, ok := key.Interface().(string); ok {
fieldName = opt.NameConvert(name)
key = reflect.ValueOf(fieldName)
}
if opt.ignoreEmpty && value.IsZero() {
continue
}
var copitedValue reflect.Value var copitedValue reflect.Value
switch dstType.Elem().Kind() { switch dstType.Elem().Kind() {
case reflect.Interface: case reflect.Interface:
@@ -234,12 +243,12 @@ func copyMap(dst, src reflect.Value, depth int, opt *options) error {
copitedValue = reflect.New(value.Type()).Elem() copitedValue = reflect.New(value.Type()).Elem()
} }
set(copitedValue, value) set(copitedValue, value, fieldName, opt)
default: default:
copitedValue = reflect.New(dstType.Elem()).Elem() copitedValue = reflect.New(dstType.Elem()).Elem()
} }
if err := deepCopy(copitedValue, value, depth+1, opt); err != nil { if err := deepCopy(copitedValue, value, depth+1, fieldName, opt); err != nil {
return err return err
} }
@@ -249,7 +258,7 @@ func copyMap(dst, src reflect.Value, depth int, opt *options) error {
return nil return nil
} }
func copySlice(dst, src reflect.Value, depth int, opt *options) error { func copySlice(dst, src reflect.Value, depth int, fieldName string, opt *options) error {
len := src.Len() 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()
@@ -262,7 +271,7 @@ func copySlice(dst, src reflect.Value, depth int, opt *options) error {
} }
for i := 0; i < len; i++ { for i := 0; i < len; i++ {
if err := deepCopy(dst.Index(i), src.Index(i), depth, opt); err != nil { if err := deepCopy(dst.Index(i), src.Index(i), depth, fieldName, opt); err != nil {
return err return err
} }
} }
@@ -302,14 +311,14 @@ func copyStruct(dst, src reflect.Value, depth int, opt *options) error {
continue continue
} }
if err := deepCopy(dstValue, sField, depth+1, opt); err != nil { if err := deepCopy(dstValue, sField, depth+1, name, opt); err != nil {
return err return err
} }
} }
return nil return nil
} }
func set(dst, src reflect.Value) error { func set(dst, src reflect.Value, fieldName string, opt *options) error {
if !src.IsValid() { if !src.IsValid() {
return ErrInvalidCopyFrom return ErrInvalidCopyFrom
} }
@@ -318,6 +327,12 @@ func set(dst, src reflect.Value) error {
src = src.Elem() src = src.Elem()
} }
if ok, err := lookupAndCopyWithConverter(dst, src, fieldName, opt); err != nil {
return err
} else if ok {
return nil
}
if dst.Kind() == reflect.Pointer { if dst.Kind() == reflect.Pointer {
if dst.IsNil() { if dst.IsNil() {
if !dst.CanSet() { if !dst.CanSet() {
@@ -404,6 +419,41 @@ func set(dst, src reflect.Value) error {
return nil return nil
} }
func lookupAndCopyWithConverter(dst, src reflect.Value, fieldName string, opt *options) (bool, error) {
if cnv, ok := opt.convertByName[fieldName]; ok {
return convert(dst, src, cnv)
}
if len(opt.converters) > 0 {
pair := converterPair{
SrcType: src.Type(),
DstType: dst.Type(),
}
if cnv, ok := opt.converters[pair]; ok {
return convert(dst, src, cnv)
}
}
return false, nil
}
func convert(dst, src reflect.Value, fn convertFunc) (bool, error) {
result, err := fn(src.Interface())
if err != nil {
return false, err
}
if result != nil {
dst.Set(reflect.ValueOf(result))
} else {
dst.Set(reflect.Zero(dst.Type()))
}
return true, nil
}
func getFieldName(field reflect.StructField) string { func getFieldName(field reflect.StructField) string {
if tag := field.Tag.Get("json"); tag != "" { if tag := field.Tag.Get("json"); tag != "" {
if commaIndex := strings.Index(tag, ","); commaIndex != -1 { if commaIndex := strings.Index(tag, ","); commaIndex != -1 {

View File

@@ -10,23 +10,32 @@ import (
) )
func TestCopyMap(t *testing.T) { func TestCopyMap(t *testing.T) {
type person struct {
Name string
Age int
}
src := map[string]any{ src := map[string]any{
"John": person{ "name": "Alice",
Name: "John", "age": 25,
Age: 30, "height": 165.5,
"is_student": true,
"birthday": "1998-05-15T06:00:00Z",
"tags": []any{"developer", "golang", "backend"},
"scores": []any{95, 88, 92},
"address": map[string]any{
"street": "123 Main St",
"city": "Beijing",
"country": "China",
}, },
"metadata": map[string]any{
"department": "Engineering",
"level": 3,
"projects": []any{"projectA", "projectB"},
},
"ptr_field": "pointer value",
} }
dst := map[string]person{} dst := map[string]any{}
assert.NoError(t, Copy(&dst, src)) assert.NoError(t, Copy(&dst, src))
fmt.Println(src, dst) // fmt.Println(src, dst)
fmt.Println(json.Struct2JsonIndent(dst)) fmt.Println(json.Struct2JsonIndent(dst))
} }
@@ -43,6 +52,18 @@ func TestMapAnyCopy(t *testing.T) {
fmt.Println(src, dst) fmt.Println(src, dst)
} }
func TestIntMapCopy(t *testing.T) {
src := map[int]string{
1: "department",
3: "level",
}
dst := map[int]any{}
assert.NoError(t, Copy(&dst, src))
fmt.Println(src, dst)
}
func TestDiffStructMapCopy(t *testing.T) { func TestDiffStructMapCopy(t *testing.T) {
type person struct { type person struct {
Name string Name string

View File

@@ -3,6 +3,7 @@ package copier
import ( import (
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -44,6 +45,28 @@ func TestCopySameStruct(t *testing.T) {
fmt.Println("src,dst:", src, dst) fmt.Println("src,dst:", src, dst)
} }
func TestCopyToNamedStruct(t *testing.T) {
type Person3 struct {
Name string `copier:"toname=Name2"`
Age int
}
type Person4 struct {
Name2 string
Age int
}
src := &Person3{
Name: "John",
Age: 30,
}
var dst Person4
assert.NoError(t, Copy(&dst, src))
t.Log("src,dst:", src, dst)
}
func TestCopyDiffStruct(t *testing.T) { func TestCopyDiffStruct(t *testing.T) {
src := &Person{ src := &Person{
Name: "John", Name: "John",
@@ -223,3 +246,27 @@ func TestAnonymousFields(t *testing.T) {
}) })
} }
func TestTypeConvert(t *testing.T) {
type Book struct {
Name string
Date time.Time
}
type Book2 struct {
Name string
Date string
}
var src = Book{
Name: "Book1",
Date: time.Now(),
}
var dst Book2
assert.NoError(t, Copy(&dst, &src, WithTypeConvertByName("Date", func(src any) (dst any, err error) {
return src.(time.Time).Format("2006-01-02"), nil
})))
t.Log("src,dst:", src, dst)
}

View File

@@ -6,16 +6,18 @@ const (
noDepthLimited = -1 noDepthLimited = -1
) )
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标识的字段
convertersByName map[string]TypeConverter // 根据名称处理的类型转换器 fieldNameMapping map[string]string // 字段名转映射
converters []TypeConverter // 根据源和目标类型处理的类型转换器 nameConverter func(string) string // 字段名转换函数
fieldNameMapping map[string]string // 字段名转映射 converters map[converterPair]convertFunc // 根据源和目标类型处理的类型转换器v
nameConverter func(string) string // 字段名转换器 convertByName map[string]convertFunc // 根据名称处理的类型转换器
} }
type option func(*options) type option func(*options)
@@ -27,16 +29,18 @@ type TypeConverter struct {
Fn func(src any) (dst any, err error) Fn func(src any) (dst any, err error)
} }
type FieldNameConverter struct { type converterPair struct {
SrcFieldName string Name string
DstFieldName string SrcType reflect.Type
DstType reflect.Type
} }
func getOpt(opts ...option) *options { func getOpt(opts ...option) *options {
opt := &options{ opt := &options{
maxDepth: noDepthLimited, maxDepth: noDepthLimited,
tagName: defaultTag, tagName: defaultTag,
convertersByName: make(map[string]TypeConverter), convertByName: make(map[string]convertFunc),
converters: make(map[converterPair]convertFunc),
} }
for _, o := range opts { for _, o := range opts {
@@ -58,68 +62,74 @@ func (opt *options) NameConvert(name string) string {
return name return name
} }
func (opt options) TypeConvert(value reflect.Value) (reflect.Value, bool) {
for _, c := range opt.converters {
_ = c
}
return value, false
}
func (opt *options) ExceedMaxDepth(depth int) bool { func (opt *options) ExceedMaxDepth(depth int) bool {
return opt.maxDepth != noDepthLimited && depth > opt.maxDepth return opt.maxDepth != noDepthLimited && depth > opt.maxDepth
} }
// WithMaxDepth 设置最大复制深度
func WithMaxDepth(depth int) option { func WithMaxDepth(depth int) option {
return func(o *options) { return func(o *options) {
o.maxDepth = depth o.maxDepth = depth
} }
} }
// WithIgnoreEmpty 复制时忽略空字段标识
func WithIgnoreEmpty() option { func WithIgnoreEmpty() option {
return func(o *options) { return func(o *options) {
o.ignoreEmpty = true o.ignoreEmpty = true
} }
} }
// WithCaseInsensitive 复制时大小写不敏感标识
func WithCaseSensitive() option { func WithCaseSensitive() option {
return func(o *options) { return func(o *options) {
o.caseSensitive = true o.caseSensitive = true
} }
} }
// WithMust 添加必须复制标识
func WithMust() option { func WithMust() option {
return func(o *options) { return func(o *options) {
o.must = true o.must = true
} }
} }
// 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) {
o.convertersByName[name] = TypeConverter{ o.convertByName[name] = f
FieldName: name, }
Fn: f, }
// WithTypeConvert 添加根据源和目标类型处理的类型转换器
func WithConverters(converters ...TypeConverter) option {
return func(o *options) {
for i := range converters {
pair := converterPair{
SrcType: reflect.TypeOf(converters[i].SrcType),
DstType: reflect.TypeOf(converters[i].DstType),
}
o.converters[pair] = converters[i].Fn
} }
} }
} }
func WithConverters(converters ...TypeConverter) option { // WithNameMapping 添加字段名映射
return func(o *options) {
o.converters = converters
}
}
func WithNameMapping(mappings map[string]string) option { func WithNameMapping(mappings map[string]string) option {
return func(o *options) { return func(o *options) {
o.fieldNameMapping = mappings o.fieldNameMapping = mappings
} }
} }
// WithNameConverter 添加字段名转换函数
func WithNameFn(fn func(string) string) option { func WithNameFn(fn func(string) string) option {
return func(o *options) { return func(o *options) {
o.nameConverter = fn o.nameConverter = fn
} }
} }
// WithTagName 添加标签名
func WithTagName(tagName string) option { func WithTagName(tagName string) option {
return func(o *options) { return func(o *options) {
o.tagName = tagName o.tagName = tagName