From 347b0c0e706c7daa7e7a119e761862db86f1b8a6 Mon Sep 17 00:00:00 2001 From: devfeel Date: Fri, 6 Sep 2024 10:16:01 +0800 Subject: [PATCH] #### Version 0.7.14 * Feature: Implement variable arguments based on NewMapper for flexible configuration settings when you init mapper. * Feature: Add Setting struct used to Config mapper * you can use like this: ``` go // Default Setting: // EnabledTypeChecking: false, // EnabledMapperStructField: true, // EnabledAutoTypeConvert: true, // EnabledMapperTag: true, // EnabledJsonTag: true, // EnabledCustomTag: false, // EnableFieldIgnoreTag: false, /// When you use default setting NewMapper() /// When you will change some setting NewMapper(CTypeChecking(true), CCustomTagName("-")) ``` * 2024-09-06 19:00 in ShangHai --- constant.go | 2 +- example/newmapper/main.go | 59 +++++++++++++++++++++ mapper.go | 1 + mapper_object.go | 88 ++++++++++++++------------------ mapper_object_internal.go | 16 +++--- mapper_object_test.go | 6 +++ mapper_setting.go | 104 ++++++++++++++++++++++++++++++++++++++ mapper_setting_test.go | 15 ++++++ mapper_test.go | 9 ++++ version.md | 22 ++++++++ 10 files changed, 263 insertions(+), 59 deletions(-) create mode 100644 example/newmapper/main.go create mode 100644 mapper_setting.go create mode 100644 mapper_setting_test.go diff --git a/constant.go b/constant.go index b85254b..f24051b 100644 --- a/constant.go +++ b/constant.go @@ -1,7 +1,7 @@ package mapper const ( - packageVersion = "0.7.13" + packageVersion = "0.7.14" mapperTagKey = "mapper" jsonTagKey = "json" IgnoreTagValue = "-" diff --git a/example/newmapper/main.go b/example/newmapper/main.go new file mode 100644 index 0000000..e0c90ab --- /dev/null +++ b/example/newmapper/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "github.com/devfeel/mapper" + "time" +) + +type ( + User struct { + Name string + Age int `mapper:"_Age"` + Id string `mapper:"_id"` + AA string `json:"Score,omitempty"` + Data []byte + Students []Student + Time time.Time + } + + Student struct { + Name string + Age int + Id string `mapper:"_id"` + Score string + } + + Teacher struct { + Name string + Age int + Id string `mapper:"_id"` + Level string + } +) + +func main() { + user := &User{} + userMap := &User{} + teacher := &Teacher{} + student := &Student{Name: "test", Age: 10, Id: "testId", Score: "100"} + valMap := make(map[string]interface{}) + valMap["Name"] = "map" + valMap["Age"] = 10 + valMap["_id"] = "x1asd" + valMap["Score"] = 100 + valMap["Data"] = []byte{1, 2, 3, 4} + valMap["Students"] = []byte{1, 2, 3, 4} //[]Student{*student} + valMap["Time"] = time.Now() + + mp := mapper.NewMapper(mapper.CTypeChecking(true), mapper.CMapperTag(true)) + + mp.Mapper(student, user) + mp.AutoMapper(student, teacher) + mp.MapperMap(valMap, userMap) + + fmt.Println("student:", student) + fmt.Println("user:", user) + fmt.Println("teacher", teacher) + fmt.Println("userMap:", userMap) +} diff --git a/mapper.go b/mapper.go index dbf457b..c3fdc19 100644 --- a/mapper.go +++ b/mapper.go @@ -23,6 +23,7 @@ type IMapper interface { GetTypeName(obj interface{}) string GetFieldName(objElem reflect.Value, index int) string + GetCustomTagName() string GetDefaultTimeWrapper() *TimeWrapper CheckExistsField(elem reflect.Value, fieldName string) (realFieldName string, exists bool) diff --git a/mapper_object.go b/mapper_object.go index cc843e3..c4a154e 100644 --- a/mapper_object.go +++ b/mapper_object.go @@ -9,42 +9,25 @@ import ( ) type mapperObject struct { - ZeroValue reflect.Value - DefaultTimeWrapper *TimeWrapper - typeWrappers []TypeWrapper - timeType reflect.Type - jsonTimeType reflect.Type - fieldNameMap sync.Map - registerMap sync.Map - enabledTypeChecking bool - enabledMapperStructField bool - enabledAutoTypeConvert bool - enabledMapperTag bool - enabledJsonTag bool - - enabledCustomTag bool - customTagName string - - // in the version < 0.7.8, we use field name as the key when mapping structs if field tag is "-" - // from 0.7.8, we add switch enableIgnoreFieldTag which is false in default - // if caller enable this flag, the field will be ignored in the mapping process - enableFieldIgnoreTag bool -} - -func NewMapper() IMapper { + ZeroValue reflect.Value + DefaultTimeWrapper *TimeWrapper + typeWrappers []TypeWrapper + timeType reflect.Type + jsonTimeType reflect.Type + fieldNameMap sync.Map + registerMap sync.Map + setting *Setting +} + +func NewMapper(opts ...Option) IMapper { + setting := NewSetting(opts...) dm := mapperObject{ - ZeroValue: reflect.Value{}, - DefaultTimeWrapper: NewTimeWrapper(), - typeWrappers: []TypeWrapper{}, - timeType: reflect.TypeOf(time.Now()), - jsonTimeType: reflect.TypeOf(JSONTime(time.Now())), - enabledTypeChecking: false, - enabledMapperStructField: true, - enabledAutoTypeConvert: true, - enabledMapperTag: true, - enabledJsonTag: true, - enabledCustomTag: false, - enableFieldIgnoreTag: false, // 保留老版本默认行为:对于tag = “-”的字段使用FieldName + ZeroValue: reflect.Value{}, + DefaultTimeWrapper: NewTimeWrapper(), + typeWrappers: []TypeWrapper{}, + timeType: reflect.TypeOf(time.Now()), + jsonTimeType: reflect.TypeOf(JSONTime(time.Now())), + setting: setting, } dm.useWrapper(dm.DefaultTimeWrapper) return &dm @@ -122,46 +105,46 @@ func (dm *mapperObject) MapperMap(fromMap map[string]interface{}, toObj interfac // if set true, the field type will be checked for consistency during mapping // default is false func (dm *mapperObject) SetEnabledTypeChecking(isEnabled bool) { - dm.enabledTypeChecking = isEnabled + dm.setting.EnabledTypeChecking = isEnabled } func (dm *mapperObject) IsEnabledTypeChecking() bool { - return dm.enabledTypeChecking + return dm.setting.EnabledTypeChecking } // SetEnabledMapperTag set enabled flag for 'Mapper' tag check // if set true, 'Mapper' tag will be check during mapping's GetFieldName // default is true func (dm *mapperObject) SetEnabledMapperTag(isEnabled bool) { - dm.enabledMapperTag = isEnabled + dm.setting.EnabledMapperTag = isEnabled dm.cleanRegisterValue() } func (dm *mapperObject) IsEnabledMapperTag() bool { - return dm.enabledMapperTag + return dm.setting.EnabledMapperTag } // SetEnabledJsonTag set enabled flag for 'Json' tag check // if set true, 'Json' tag will be check during mapping's GetFieldName // default is true func (dm *mapperObject) SetEnabledJsonTag(isEnabled bool) { - dm.enabledJsonTag = isEnabled + dm.setting.EnabledJsonTag = isEnabled dm.cleanRegisterValue() } func (dm *mapperObject) IsEnabledJsonTag() bool { - return dm.enabledJsonTag + return dm.setting.EnabledJsonTag } // SetEnabledAutoTypeConvert set enabled flag for auto type convert // if set true, field will auto convert in Time and Unix // default is true func (dm *mapperObject) SetEnabledAutoTypeConvert(isEnabled bool) { - dm.enabledAutoTypeConvert = isEnabled + dm.setting.EnabledAutoTypeConvert = isEnabled } func (dm *mapperObject) IsEnabledAutoTypeConvert() bool { - return dm.enabledAutoTypeConvert + return dm.setting.EnabledAutoTypeConvert } // SetEnabledMapperStructField set enabled flag for MapperStructField @@ -171,28 +154,28 @@ func (dm *mapperObject) IsEnabledAutoTypeConvert() bool { // 2. fromField and toField must be not same type // default is enabled func (dm *mapperObject) SetEnabledMapperStructField(isEnabled bool) { - dm.enabledMapperStructField = isEnabled + dm.setting.EnabledMapperStructField = isEnabled } func (dm *mapperObject) IsEnabledMapperStructField() bool { - return dm.enabledMapperStructField + return dm.setting.EnabledMapperStructField } // SetEnabledCustomTag set enabled flag for set custom tag name // if set true and set customTagName, the custom tag will be check during mapping's GetFieldName // default is false func (dm *mapperObject) SetEnabledCustomTag(isEnabled bool) { - dm.enabledCustomTag = isEnabled + dm.setting.EnabledCustomTag = isEnabled dm.cleanRegisterValue() } func (dm *mapperObject) IsEnabledCustomTag() bool { - return dm.enabledCustomTag + return dm.setting.EnabledCustomTag } // SetCustomTagName func (dm *mapperObject) SetCustomTagName(tagName string) { - dm.customTagName = tagName + dm.setting.CustomTagName = tagName } // SetEnableFieldIgnoreTag set the enabled flag for the ignored tag @@ -200,11 +183,11 @@ func (dm *mapperObject) SetCustomTagName(tagName string) { // from 0.7.8, we add switch enableFieldIgnoreTag which is false in default // if caller enable this flag, the field will be ignored in the mapping process func (dm *mapperObject) SetEnableFieldIgnoreTag(isEnabled bool) { - dm.enableFieldIgnoreTag = isEnabled + dm.setting.EnableFieldIgnoreTag = isEnabled } func (dm *mapperObject) IsEnableFieldIgnoreTag() bool { - return dm.enableFieldIgnoreTag + return dm.setting.EnableFieldIgnoreTag } // GetTypeName get type name @@ -213,6 +196,11 @@ func (dm *mapperObject) GetTypeName(obj interface{}) string { return object.String() } +// GetCustomTagName get CustomTagName +func (dm *mapperObject) GetCustomTagName() string { + return dm.setting.CustomTagName +} + // GetFieldName get fieldName with ElemValue and index // if config tag string, return tag value func (dm *mapperObject) GetFieldName(objElem reflect.Value, index int) string { diff --git a/mapper_object_internal.go b/mapper_object_internal.go index e4cf963..7dcd675 100644 --- a/mapper_object_internal.go +++ b/mapper_object_internal.go @@ -135,13 +135,13 @@ func (dm *mapperObject) convertstructfieldInternal(fieldName string, fromFieldIn toFieldInfo := toElem.FieldByName(realFieldName) // check field is same type - if dm.enabledTypeChecking { + if dm.setting.EnabledTypeChecking { if fromFieldInfo.Kind() != toFieldInfo.Kind() { return nil } } - if dm.enabledMapperStructField && + if dm.setting.EnabledMapperStructField && toFieldInfo.Kind() == reflect.Struct && fromFieldInfo.Kind() == reflect.Struct && toFieldInfo.Type() != fromFieldInfo.Type() && !dm.checkIsTypeWrapper(toFieldInfo) && !dm.checkIsTypeWrapper(fromFieldInfo) { @@ -154,7 +154,7 @@ func (dm *mapperObject) convertstructfieldInternal(fieldName string, fromFieldIn } } else { isSet := false - if dm.enabledAutoTypeConvert { + if dm.setting.EnabledAutoTypeConvert { if dm.DefaultTimeWrapper.IsType(fromFieldInfo) && toFieldInfo.Kind() == reflect.Int64 { fromTime := fromFieldInfo.Interface().(time.Time) toFieldInfo.Set(reflect.ValueOf(TimeToUnix(fromTime))) @@ -263,7 +263,7 @@ func (dm *mapperObject) setFieldValue(fieldValue reflect.Value, fieldKind reflec case string: timeString = d case int64: - if dm.enabledAutoTypeConvert { + if dm.setting.EnabledAutoTypeConvert { // try to transform Unix time to local Time t, err := UnixToTimeLocation(value.(int64), time.UTC.String()) if err != nil { @@ -303,7 +303,7 @@ func (dm *mapperObject) setFieldValue(fieldValue reflect.Value, fieldKind reflec func (dm *mapperObject) getStructTag(field reflect.StructField) string { tagValue := "" // 1.check mapperTagKey - if dm.enabledMapperTag { + if dm.setting.EnabledMapperTag { tagValue = field.Tag.Get(mapperTagKey) if tagValue != "" { return tagValue @@ -311,7 +311,7 @@ func (dm *mapperObject) getStructTag(field reflect.StructField) string { } // 2.check jsonTagKey - if dm.enabledJsonTag { + if dm.setting.EnabledJsonTag { tagValue = field.Tag.Get(jsonTagKey) if tagValue != "" { // support more tag property, as json tag omitempty 2018-07-13 @@ -321,8 +321,8 @@ func (dm *mapperObject) getStructTag(field reflect.StructField) string { // 3.che // ck customTag - if dm.enabledCustomTag { - tagValue = field.Tag.Get(dm.customTagName) + if dm.setting.EnabledCustomTag { + tagValue = field.Tag.Get(dm.setting.CustomTagName) if tagValue != "" { return tagValue } diff --git a/mapper_object_test.go b/mapper_object_test.go index fac2bc5..a0312c1 100644 --- a/mapper_object_test.go +++ b/mapper_object_test.go @@ -1,6 +1,7 @@ package mapper import ( + "fmt" "reflect" "strconv" "sync" @@ -8,6 +9,11 @@ import ( "time" ) +func TestMapperObject_NewMapper(t *testing.T) { + m := NewMapper(CTypeChecking(true), CCustomTagName("^")) + fmt.Println(m) +} + func Test_Object_SetEnabledTypeChecking(t *testing.T) { m := NewMapper() m.SetEnabledTypeChecking(true) diff --git a/mapper_setting.go b/mapper_setting.go new file mode 100644 index 0000000..dbe2ed8 --- /dev/null +++ b/mapper_setting.go @@ -0,0 +1,104 @@ +package mapper + +// Option 用于设置 Config 结构体的字段。 +type Option func(*Setting) + +type Setting struct { + EnabledTypeChecking bool + EnabledMapperStructField bool + EnabledAutoTypeConvert bool + EnabledMapperTag bool + EnabledJsonTag bool + EnabledCustomTag bool + CustomTagName string + + // in the version < 0.7.8, we use field name as the key when mapping structs if field tag is "-" + // from 0.7.8, we add switch enableIgnoreFieldTag which is false in default + // if caller enable this flag, the field will be ignored in the mapping process + EnableFieldIgnoreTag bool +} + +// getDefaultSetting return default mapper setting +// Use default value: +// +// EnabledTypeChecking: false, +// EnabledMapperStructField: true, +// EnabledAutoTypeConvert: true, +// EnabledMapperTag: true, +// EnabledJsonTag: true, +// EnabledCustomTag: false, +// EnableFieldIgnoreTag: false +func getDefaultSetting() *Setting { + return &Setting{ + EnabledTypeChecking: false, + EnabledMapperStructField: true, + EnabledAutoTypeConvert: true, + EnabledMapperTag: true, + EnabledJsonTag: true, + EnabledCustomTag: false, + EnableFieldIgnoreTag: false, // 保留老版本默认行为:对于tag = “-”的字段使用FieldName + } +} + +// NewSetting create new setting with multi option +func NewSetting(opts ...Option) *Setting { + cfg := getDefaultSetting() + for _, opt := range opts { + opt(cfg) + } + return cfg +} + +// CTypeChecking set EnabledTypeChecking value +// +// Default value: false +func CTypeChecking(isEnabled bool) Option { + return func(c *Setting) { + c.EnabledTypeChecking = isEnabled + } +} + +// CMapperTag set EnabledMapperTag value +// +// Default value: true +func CMapperTag(isEnabled bool) Option { + return func(c *Setting) { + c.EnabledMapperTag = isEnabled + } +} + +func CJsonTag(isEnabled bool) Option { + return func(c *Setting) { + c.EnabledJsonTag = isEnabled + } +} + +func CAutoTypeConvert(isEnabled bool) Option { + return func(c *Setting) { + c.EnabledAutoTypeConvert = isEnabled + } +} + +func CMapperStructField(isEnabled bool) Option { + return func(c *Setting) { + c.EnabledMapperStructField = isEnabled + } +} + +func CCustomTag(isEnabled bool) Option { + return func(c *Setting) { + c.EnabledCustomTag = isEnabled + } +} + +func CFieldIgnoreTag(isEnabled bool) Option { + return func(c *Setting) { + c.EnableFieldIgnoreTag = isEnabled + } +} + +func CCustomTagName(tagName string) Option { + return func(c *Setting) { + c.CustomTagName = tagName + } +} diff --git a/mapper_setting_test.go b/mapper_setting_test.go new file mode 100644 index 0000000..f12e3d5 --- /dev/null +++ b/mapper_setting_test.go @@ -0,0 +1,15 @@ +package mapper + +import "testing" + +func TestNewSetting(t *testing.T) { + checkEnabledTypeChecking := true + checkEnabledAutoTypeConvert := false + + setting := NewSetting(CTypeChecking(checkEnabledTypeChecking), CAutoTypeConvert(checkEnabledAutoTypeConvert)) + if setting.EnabledTypeChecking != checkEnabledTypeChecking || setting.EnabledAutoTypeConvert != checkEnabledAutoTypeConvert { + t.Error("NewSetting error: [", checkEnabledTypeChecking, ",", setting.EnabledTypeChecking, "],[", checkEnabledAutoTypeConvert, ",", setting.EnabledAutoTypeConvert, "]") + } else { + t.Log("NewSetting success") + } +} diff --git a/mapper_test.go b/mapper_test.go index 583909f..8b19c90 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -42,6 +42,15 @@ func init() { testValue = reflect.ValueOf(&testStruct{}) } +func TestPackageVersion(t *testing.T) { + v := PackageVersion() + if v != packageVersion { + t.Error("PackageVersion error: not equal with packageVersion[" + packageVersion + "]") + } else { + t.Log("PackageVersion success") + } +} + func Test_CheckIsTypeWrapper(t *testing.T) { v := TagStruct{} if standardMapper.CheckIsTypeWrapper(reflect.ValueOf(v)) == true { diff --git a/version.md b/version.md index 8f472fa..3dd8db5 100644 --- a/version.md +++ b/version.md @@ -1,5 +1,27 @@ ## devfeel/mapper +#### Version 0.7.14 +* Feature: Implement variable arguments based on NewMapper for flexible configuration settings when you init mapper. +* Feature: Add Setting struct used to Config mapper +* you can use like this: +``` go + // Default Setting: + // EnabledTypeChecking: false, + // EnabledMapperStructField: true, + // EnabledAutoTypeConvert: true, + // EnabledMapperTag: true, + // EnabledJsonTag: true, + // EnabledCustomTag: false, + // EnableFieldIgnoreTag: false, + + /// When you use default setting + NewMapper() + + /// When you will change some setting + NewMapper(CTypeChecking(true), CCustomTagName("-")) +``` +* 2024-09-06 19:00 in ShangHai + #### Version 0.7.13 * Feature: Added the "composite-field" tag to continue expanding and searching for corresponding field mappings when encountering composite fields in a Struct. Currently, only one level of expansion is supported. * Tips: Thanks to @naeemaei for issue #39