Skip to content

Commit

Permalink
Merge pull request #190 from driventokill/feature/custom-field-names
Browse files Browse the repository at this point in the history
feat: Support custom file name mappings
  • Loading branch information
jinzhu committed Aug 9, 2023
2 parents 83982c7 + 1835b1a commit 70b1d4e
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Expand Up @@ -12,7 +12,7 @@ jobs:
ci:
strategy:
matrix:
go: ['1.16', '1.17', '1.18', '1.19']
go: ['1.17', '1.18', '1.19', '1.20', '1.21']
platform: [ubuntu-latest, macos-latest] # can not run in windows OS
runs-on: ${{ matrix.platform }}

Expand Down
54 changes: 51 additions & 3 deletions copier.go
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
2 changes: 1 addition & 1 deletion copier_different_type_test.go
Expand Up @@ -133,7 +133,7 @@ func TestAssignableType(t *testing.T) {

ts3 := &TypeStruct3{}

copier.Copy(&ts3, &ts)
copier.CopyWithOption(&ts3, &ts, copier.Option{CaseSensitive: true})

if v, ok := ts3.Field1.(string); !ok {
t.Error("Assign to interface{} type did not succeed")
Expand Down
47 changes: 47 additions & 0 deletions copier_field_name_mapping_test.go
@@ -0,0 +1,47 @@
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 70b1d4e

Please sign in to comment.