Skip to content

Commit

Permalink
feat: Support custom file name mappings
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
driventokill committed Aug 8, 2023
1 parent 83982c7 commit 7d39583
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 3 deletions.
54 changes: 51 additions & 3 deletions copier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
44 changes: 44 additions & 0 deletions copier_field_name_mapping_test.go
Original file line number Diff line number Diff line change
@@ -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.")
}
}

0 comments on commit 7d39583

Please sign in to comment.