From 7d3958397bf5f3882aa4c60a347f07b5ac346187 Mon Sep 17 00:00:00 2001 From: Yang Fang Date: Wed, 9 Aug 2023 01:10:59 +0800 Subject: [PATCH] feat: Support custom file name mappings Custom field name mappings to copy values with different names in `fromValue` and `toValue` types. Examples can be found in `copier_different_field_name_test.go`. --- copier.go | 54 +++++++++++++++++++++++++++++-- copier_field_name_mapping_test.go | 44 +++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 copier_field_name_mapping_test.go diff --git a/copier.go b/copier.go index bfa8308..43a14f1 100644 --- a/copier.go +++ b/copier.go @@ -41,6 +41,9 @@ type Option struct { 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 { @@ -70,6 +73,27 @@ type converterPair struct { 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 @@ -100,6 +124,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) from = indirect(reflect.ValueOf(fromValue)) to = indirect(reflect.ValueOf(toValue)) converters = opt.converters() + mappings = opt.fieldNameMapping() ) if !to.CanAddr() { @@ -298,7 +323,9 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) continue } - srcFieldName, destFieldName := getFieldName(name, flgs) + fieldNamesMapping := getFieldNamesMapping(mappings, fromType, toType) + + srcFieldName, destFieldName := getFieldName(name, flgs, fieldNamesMapping) if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, opt.IgnoreEmpty) { // process for nested anonymous field destFieldNotSet := false @@ -365,7 +392,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) // Copy from from method to dest field for _, field := range deepFields(toType) { name := field.Name - srcFieldName, destFieldName := getFieldName(name, flgs) + srcFieldName, destFieldName := getFieldName(name, flgs, getFieldNamesMapping(mappings, fromType, toType)) var fromMethod reflect.Value if source.CanAddr() { @@ -429,6 +456,21 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) 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 { @@ -727,8 +769,14 @@ func checkBitFlags(flagsList map[string]uint8) (err error) { return } -func getFieldName(fieldName string, flgs flags) (srcFieldName string, destFieldName string) { +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 { diff --git a/copier_field_name_mapping_test.go b/copier_field_name_mapping_test.go new file mode 100644 index 0000000..755cf0d --- /dev/null +++ b/copier_field_name_mapping_test.go @@ -0,0 +1,44 @@ +package copier_test + +import ( + "github.com/jinzhu/copier" + "reflect" + "testing" +) + +func TestCustomFieldName(t *testing.T) { + type User1 struct { + Id int64 + Name string + Address []string + } + + type User2 struct { + Id2 int64 + Name2 string + Address2 []string + } + + u1 := User1{Id: 1, Name: "1", Address: []string{"1"}} + var u2 User2 + err := copier.CopyWithOption(&u2, u1, + copier.Option{FieldNameMapping: []copier.FieldNameMapping{ + {SrcType: u1, DstType: u2, Mapping: map[string]string{"Id": "Id2", "Name": "Name2", "Address": "Address2"}}, + }}) + + if err != nil { + t.Fatal(err) + } + + if u1.Id != u2.Id2 { + t.Error("copy id failed.") + } + + if u1.Name != u2.Name2 { + t.Error("copy name failed.") + } + + if !reflect.DeepEqual(u1.Address, u2.Address2) { + t.Error("copy address failed.") + } +}