From 2cd8afa71368e619d08e149b5b2c95060c879bd8 Mon Sep 17 00:00:00 2001 From: shyandsy Date: Fri, 15 Apr 2022 23:26:17 +0800 Subject: [PATCH 1/3] feature: object oriented api for the mapper --- README.md | 70 +++++- constant.go | 12 + convert.go | 5 +- example/object/go.mod | 7 + example/object/go.sum | 0 example/object/main.go | 39 +++ example/typewrapper/main.go | 18 +- go.mod | 5 + go.sum | 2 + mapper.go | 52 ++-- mapper_object.go | 341 ++++++++++++++++++++++++++ mapper_object_internal.go | 319 +++++++++++++++++++++++++ mapper_object_test.go | 465 ++++++++++++++++++++++++++++++++++++ version.md | 50 ++++ 14 files changed, 1356 insertions(+), 29 deletions(-) create mode 100644 constant.go create mode 100644 example/object/go.mod create mode 100644 example/object/go.sum create mode 100644 example/object/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 mapper_object.go create mode 100644 mapper_object_internal.go create mode 100644 mapper_object_test.go diff --git a/README.md b/README.md index f78561e..9eb4bda 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # devfeel/mapper + A simple and easy go tools for auto mapper struct to map, struct to struct, slice to slice, map to slice, map to json. ## 1. Install @@ -8,6 +9,8 @@ go get -u github.com/devfeel/mapper ``` ## 2. Getting Started + +Traditional Usage ```go package main @@ -66,8 +69,8 @@ func main() { fmt.Println("teacher", teacher) fmt.Println("userMap:", userMap) } - ``` + 执行main,输出: ``` student: &{test 10 testId 100} @@ -76,7 +79,59 @@ teacher &{test 10 testId } userMap: &{map 10 x1asd 100 2017-11-20 13:45:56.3972504 +0800 CST m=+0.006004001} ``` +Object Usage + +```go +package main + +import ( + "fmt" + "github.com/devfeel/mapper" +) + +type ( + User struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"age"` + } + + Student struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"-"` + } +) + +func main() { + user := &User{Name: "test", Age: 10} + student := &Student{} + + // create mapper object + m := mapper.NewMapper() + + // enable the type checking + m.SetEnabledTypeChecking(true) + + student.Age = 1 + + // disable the json tag + m.SetEnabledJsonTag(false) + + // student::age should be 1 + m.Mapper(user, student) + + fmt.Println(student) +} +``` + +执行main,输出: +``` +&{test 1} +``` + + + ## Features + * 支持不同结构体相同名称相同类型字段自动赋值,使用Mapper * 支持不同结构体Slice的自动赋值,使用MapperSlice * 支持字段为结构体时的自动赋值 @@ -88,4 +143,15 @@ userMap: &{map 10 x1asd 100 2017-11-20 13:45:56.3972504 +0800 CST m=+0.006004001 * 支持tag标签,tag关键字为 mapper * 兼容json-tag标签 * 当tag为"-"时,将忽略tag定义,使用struct field name -* 无需手动Register struct,内部自动识别 \ No newline at end of file +* 无需手动Register struct,内部自动识别 +* 支持开启关闭 + * SetEnabledTypeChecking(bool) // 类型检查 + * IsEnabledTypeChecking + * SetEnabledMapperTag // mapper tag + * IsEnabledMapperTag + * SetEnabledJsonTag // json tag + * IsEnabledJsonTag + * SetEnabledAutoTypeConvert // auto type convert + * IsEnabledAutoTypeConvert + * SetEnabledMapperStructField // mapper struct field + * IsEnabledMapperStructField \ No newline at end of file diff --git a/constant.go b/constant.go new file mode 100644 index 0000000..587fd79 --- /dev/null +++ b/constant.go @@ -0,0 +1,12 @@ +package mapper + +const ( + packageVersion = "0.7.6" + mapperTagKey = "mapper" + jsonTagKey = "json" + IgnoreTagValue = "-" + nameConnector = "_" + formatTime = "15:04:05" + formatDate = "2006-01-02" + formatDateTime = "2006-01-02 15:04:05" +) \ No newline at end of file diff --git a/convert.go b/convert.go index b7619da..c1e34ab 100644 --- a/convert.go +++ b/convert.go @@ -1,6 +1,7 @@ package mapper import ( + "encoding/hex" "fmt" "math/big" "reflect" @@ -22,12 +23,12 @@ func (f *Convert) Set(v string) { // Clear string func (f *Convert) Clear() { - *f = Convert(0x1E) + *f = Convert(hex.EncodeToString([]byte{0x1E})) } // Exist check string exist func (f Convert) Exist() bool { - return string(f) != string(0x1E) + return string(f) != hex.EncodeToString([]byte{0x1E}) } // Bool string to bool diff --git a/example/object/go.mod b/example/object/go.mod new file mode 100644 index 0000000..6734ac5 --- /dev/null +++ b/example/object/go.mod @@ -0,0 +1,7 @@ +module object + +go 1.16 + +replace github.com/devfeel/mapper v0.7.6 => ./../../../mapper + +require github.com/devfeel/mapper v0.7.6 diff --git a/example/object/go.sum b/example/object/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/example/object/main.go b/example/object/main.go new file mode 100644 index 0000000..583d788 --- /dev/null +++ b/example/object/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "github.com/devfeel/mapper" +) + +type ( + User struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"age"` + } + + Student struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"-"` + } +) + +func main() { + user := &User{Name: "test", Age: 10} + student := &Student{} + + // create mapper object + m := mapper.NewMapper() + + // enable the type checking + m.SetEnabledTypeChecking(true) + + student.Age = 1 + + // disable the json tag + m.SetEnabledJsonTag(false) + + // student::age should be 1 + m.Mapper(user, student) + + fmt.Println(student) +} diff --git a/example/typewrapper/main.go b/example/typewrapper/main.go index 89148a6..696e27d 100644 --- a/example/typewrapper/main.go +++ b/example/typewrapper/main.go @@ -1,8 +1,6 @@ package main import ( - "fmt" - "github.com/devfeel/mapper" "reflect" "time" ) @@ -28,7 +26,7 @@ type decimal struct { } type DecimalWrapper struct { - mapper.BaseTypeWrapper + // mapper.BaseTypeWrapper } func (w *DecimalWrapper) IsType(value reflect.Value) bool { @@ -39,12 +37,14 @@ func (w *DecimalWrapper) IsType(value reflect.Value) bool { } func main() { - mapper.UseWrapper(&DecimalWrapper{}) - user := &User{Name: "test", Age: 10, Score: decimal{value: 1}, Time: time.Now()} - stu := &Student{} + /* + mapper.UseWrapper(&DecimalWrapper{}) + user := &User{Name: "test", Age: 10, Score: decimal{value: 1}, Time: time.Now()} + stu := &Student{} - mapper.AutoMapper(user, stu) + mapper.AutoMapper(user, stu) - fmt.Println(user) - fmt.Println(stu) + fmt.Println(user) + fmt.Println(stu) + */ } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6ff406d --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module mapper + +go 1.16 + +require github.com/devfeel/mapper v0.7.6 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1c15dea --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/devfeel/mapper v0.7.6 h1:FItOzW4YpEp1x+U1izauW5+sbVIiymOwF7AiWxIdUVk= +github.com/devfeel/mapper v0.7.6/go.mod h1:foz4u16jrssGoDfnWYQGFcthjlU6uBV5UV8uYJfKneA= diff --git a/mapper.go b/mapper.go index ee19c01..0bb7d5a 100644 --- a/mapper.go +++ b/mapper.go @@ -23,16 +23,36 @@ var ( typeWrappers []TypeWrapper ) -const ( - packageVersion = "0.7.6" - mapperTagKey = "mapper" - jsonTagKey = "json" - IgnoreTagValue = "-" - nameConnector = "_" - formatTime = "15:04:05" - formatDate = "2006-01-02" - formatDateTime = "2006-01-02 15:04:05" -) +type IMapper interface { + Mapper(fromObj, toObj interface{}) error + AutoMapper(fromObj, toObj interface{}) error + MapperMap(fromMap map[string]interface{}, toObj interface{}) error + Register(obj interface{}) error + GetTypeName(obj interface{}) string + GetFieldName(objElem reflect.Value, index int) string + GetDefaultTimeWrapper() *TimeWrapper + CheckExistsField(elem reflect.Value, fieldName string) (realFieldName string, exists bool) + MapToSlice(fromMap map[string]interface{}, toSlice interface{}) error + MapperMapSlice(fromMaps map[string]map[string]interface{}, toSlice interface{}) error + MapperSlice(fromSlice, toSlice interface{}) error + MapToJson(fromMap map[string]interface{}) ([]byte, error) + JsonToMap(body []byte, toMap *map[string]interface{}) error + + SetEnabledTypeChecking(isEnabled bool) + IsEnabledTypeChecking() bool + + SetEnabledMapperTag(isEnabled bool) + IsEnabledMapperTag() bool + + SetEnabledJsonTag(isEnabled bool) + IsEnabledJsonTag() bool + + SetEnabledAutoTypeConvert(isEnabled bool) + IsEnabledAutoTypeConvert() bool + + SetEnabledMapperStructField(isEnabled bool) + IsEnabledMapperStructField() bool +} func init() { ZeroValue = reflect.Value{} @@ -136,7 +156,7 @@ func registerValue(objValue reflect.Value) error { } } - //store register flag + // store register flag registerMap.Store(typeName, nil) return nil } @@ -194,14 +214,14 @@ func MapperMap(fromMap map[string]interface{}, toObj interface{}) error { if toElem == ZeroValue { return errors.New("to obj is not legal value") } - //check register flag - //if not register, register it + // check register flag + // if not register, register it if !checkIsRegister(toElem) { Register(toObj) } for k, v := range fromMap { fieldName := k - //check field is exists + // check field is exists realFieldName, exists := CheckExistsField(toElem, fieldName) if !exists { continue @@ -274,7 +294,7 @@ func MapperMapSlice(fromMaps map[string]map[string]interface{}, toSlice interfac toElemType := reflect.TypeOf(toSlice).Elem().Elem() realType := toElemType.Kind() direct := reflect.Indirect(toValue) - //3 elem parse: 1.[]*type 2.*type 3.type + // 3 elem parse: 1.[]*type 2.*type 3.type if realType == reflect.Ptr { toElemType = toElemType.Elem() } @@ -307,7 +327,7 @@ func MapperSlice(fromSlice, toSlice interface{}) error { elemType := reflect.TypeOf(toSlice).Elem().Elem() realType := elemType.Kind() direct := reflect.Indirect(toValue) - //3 elem parse: 1.[]*type 2.*type 3.type + // 3 elem parse: 1.[]*type 2.*type 3.type if realType == reflect.Ptr { elemType = elemType.Elem() } diff --git a/mapper_object.go b/mapper_object.go new file mode 100644 index 0000000..54083ab --- /dev/null +++ b/mapper_object.go @@ -0,0 +1,341 @@ +package mapper + +import ( + "encoding/json" + "errors" + "reflect" + "sync" + "time" +) + +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 +} + +func NewMapper() IMapper { + 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, + } + dm.useWrapper(dm.DefaultTimeWrapper) + return &dm +} + +// Mapper map and set value from struct fromObj to toObj +// not support auto register struct +func (dm *mapperObject) Mapper(fromObj, toObj interface{}) error { + fromElem := reflect.ValueOf(fromObj).Elem() + toElem := reflect.ValueOf(toObj).Elem() + if fromElem == dm.ZeroValue { + return errors.New("from obj is not legal value") + } + if toElem == dm.ZeroValue { + return errors.New("to obj is not legal value") + } + return dm.elemMapper(fromElem, toElem) +} + +// AutoMapper mapper and set value from struct fromObj to toObj +// support auto register struct +func (dm *mapperObject) AutoMapper(fromObj, toObj interface{}) error { + return dm.Mapper(fromObj, toObj) +} + +// MapperMap mapper and set value from map to object +// support auto register struct +// now support field type: +// 1.reflect.Bool +// 2.reflect.String +// 3.reflect.Int8\16\32\64 +// 4.reflect.Uint8\16\32\64 +// 5.reflect.Float32\64 +// 6.time.Time +func (dm *mapperObject) MapperMap(fromMap map[string]interface{}, toObj interface{}) error { + toElemType := reflect.ValueOf(toObj) + toElem := toElemType + if toElemType.Kind() == reflect.Ptr { + toElem = toElemType.Elem() + } + + if toElem == dm.ZeroValue { + return errors.New("to obj is not legal value") + } + // check register flag + // if not register, register it + if !dm.checkIsRegister(toElem) { + if err := dm.Register(toObj); err != nil { + return err + } + } + for k, v := range fromMap { + fieldName := k + // check field is exists + realFieldName, exists := dm.CheckExistsField(toElem, fieldName) + if !exists { + continue + } + fieldInfo, exists := toElem.Type().FieldByName(realFieldName) + if !exists { + continue + } + + fieldKind := fieldInfo.Type.Kind() + fieldValue := toElem.FieldByName(realFieldName) + + if err := dm.setFieldValue(fieldValue, fieldKind, v); err != nil { + return err + } + } + return nil +} + +// SetEnabledTypeChecking set enabled flag for TypeChecking +// 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 +} + +func (dm *mapperObject) IsEnabledTypeChecking() bool { + return dm.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 +} + +func (dm *mapperObject) IsEnabledMapperTag() bool { + return dm.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 +} + +func (dm *mapperObject) IsEnabledJsonTag() bool { + return dm.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 +} + +func (dm *mapperObject) IsEnabledAutoTypeConvert() bool { + return dm.enabledAutoTypeConvert +} + +// SetEnabledMapperStructField set enabled flag for MapperStructField +// if set true, the reflect.Struct field will auto mapper +// must follow premises: +// 1. fromField and toField type must be reflect.Struct and not time.Time +// 2. fromField and toField must be not same type +// default is enabled +func (dm *mapperObject) SetEnabledMapperStructField(isEnabled bool) { + dm.enabledMapperStructField = isEnabled +} + +func (dm *mapperObject) IsEnabledMapperStructField() bool { + return dm.enabledMapperStructField +} + +// GetTypeName get type name +func (dm *mapperObject) GetTypeName(obj interface{}) string { + object := reflect.ValueOf(obj) + return object.String() +} + +// GetFieldName get fieldName with ElemValue and index +// if config tag string, return tag value +func (dm *mapperObject) GetFieldName(objElem reflect.Value, index int) string { + fieldName := "" + field := objElem.Type().Field(index) + tag := dm.getStructTag(field) + if tag != "" { + fieldName = tag + } else { + fieldName = field.Name + } + return fieldName +} + +func (dm *mapperObject) GetDefaultTimeWrapper() *TimeWrapper { + return dm.DefaultTimeWrapper +} + +// Register register struct to init Map +func (dm *mapperObject) Register(obj interface{}) error { + objValue := reflect.ValueOf(obj) + if objValue == dm.ZeroValue { + return errors.New("obj value does not exist") + } + return dm.registerValue(objValue) +} + +// MapToSlice mapper from map[string]interface{} to a slice of any type's ptr +// toSlice must be a slice of any type. +func (dm *mapperObject) MapToSlice(fromMap map[string]interface{}, toSlice interface{}) error { + var err error + toValue := reflect.ValueOf(toSlice) + if toValue.Kind() != reflect.Ptr { + return errors.New("toSlice must be a pointer to a slice") + } + if toValue.IsNil() { + return errors.New("toSlice must not be a nil pointer") + } + + toElemType := reflect.TypeOf(toSlice).Elem().Elem() + realType := toElemType.Kind() + direct := reflect.Indirect(toValue) + if realType == reflect.Ptr { + toElemType = toElemType.Elem() + } + for _, v := range fromMap { + if reflect.TypeOf(v).Kind().String() == "map" { + elem := reflect.New(toElemType) + err = dm.MapperMap(v.(map[string]interface{}), elem.Interface()) + if err == nil { + if realType == reflect.Ptr { + direct.Set(reflect.Append(direct, elem)) + } else { + direct.Set(reflect.Append(direct, elem).Elem()) + } + } + } else { + if realType == reflect.Ptr { + direct.Set(reflect.Append(direct, reflect.ValueOf(v))) + } else { + direct.Set(reflect.Append(direct, reflect.ValueOf(v).Elem())) + } + } + + } + return err +} + +// MapperMapSlice mapper from map[string]map[string]interface{} to a slice of any type's ptr +// toSlice must be a slice of any type. +// Deprecated: will remove on v1.0, please use MapToSlice instead +func (dm *mapperObject) MapperMapSlice(fromMaps map[string]map[string]interface{}, toSlice interface{}) error { + var err error + toValue := reflect.ValueOf(toSlice) + if toValue.Kind() != reflect.Ptr { + return errors.New("toSlice must be a pointer to a slice") + } + if toValue.IsNil() { + return errors.New("toSlice must not be a nil pointer") + } + + toElemType := reflect.TypeOf(toSlice).Elem().Elem() + realType := toElemType.Kind() + direct := reflect.Indirect(toValue) + // 3 elem parse: 1.[]*type 2.*type 3.type + if realType == reflect.Ptr { + toElemType = toElemType.Elem() + } + for _, v := range fromMaps { + elem := reflect.New(toElemType) + err = dm.MapperMap(v, elem.Interface()) + if err == nil { + if realType == reflect.Ptr { + direct.Set(reflect.Append(direct, elem)) + } else { + direct.Set(reflect.Append(direct, elem.Elem())) + } + } + } + return err +} + +// MapperSlice mapper from slice of struct to a slice of any type +// fromSlice and toSlice must be a slice of any type. +func (dm *mapperObject) MapperSlice(fromSlice, toSlice interface{}) error { + var err error + toValue := reflect.ValueOf(toSlice) + if toValue.Kind() != reflect.Ptr { + return errors.New("toSlice must be a pointer to a slice") + } + if toValue.IsNil() { + return errors.New("toSlice must not be a nil pointer") + } + + elemType := reflect.TypeOf(toSlice).Elem().Elem() + realType := elemType.Kind() + direct := reflect.Indirect(toValue) + // 3 elem parse: 1.[]*type 2.*type 3.type + if realType == reflect.Ptr { + elemType = elemType.Elem() + } + + fromElems := dm.convertToSlice(fromSlice) + for _, v := range fromElems { + elem := reflect.New(elemType).Elem() + if realType == reflect.Ptr { + elem = reflect.New(elemType) + } + if realType == reflect.Ptr { + err = dm.elemMapper(reflect.ValueOf(v).Elem(), elem.Elem()) + } else { + err = dm.elemMapper(reflect.ValueOf(v), elem) + } + if err == nil { + direct.Set(reflect.Append(direct, elem)) + } + } + return err +} + +// MapToJson mapper from map[string]interface{} to json []byte +func (dm *mapperObject) MapToJson(fromMap map[string]interface{}) ([]byte, error) { + jsonStr, err := json.Marshal(fromMap) + if err != nil { + return nil, err + } + return jsonStr, nil +} + +// JsonToMap mapper from json []byte to map[string]interface{} +func (dm *mapperObject) JsonToMap(body []byte, toMap *map[string]interface{}) error { + err := json.Unmarshal(body, toMap) + return err +} + +// CheckExistsField check field is exists by name +func (dm *mapperObject) CheckExistsField(elem reflect.Value, fieldName string) (realFieldName string, exists bool) { + typeName := elem.Type().String() + fileKey := typeName + nameConnector + fieldName + realName, isOk := dm.fieldNameMap.Load(fileKey) + + if !isOk { + return "", isOk + } else { + return realName.(string), isOk + } +} diff --git a/mapper_object_internal.go b/mapper_object_internal.go new file mode 100644 index 0000000..f9bc5fe --- /dev/null +++ b/mapper_object_internal.go @@ -0,0 +1,319 @@ +package mapper + +import ( + "errors" + "fmt" + "reflect" + "strings" + "time" +) + +// registerValue register Value to init Map +func (dm *mapperObject) registerValue(objValue reflect.Value) error { + regValue := objValue + if objValue == dm.ZeroValue { + return errors.New("obj value does not exist") + } + + if regValue.Type().Kind() == reflect.Ptr { + regValue = regValue.Elem() + } + + typeName := regValue.Type().String() + if regValue.Type().Kind() == reflect.Struct { + for i := 0; i < regValue.NumField(); i++ { + mapFieldName := typeName + nameConnector + dm.getFieldName(regValue, i) + realFieldName := regValue.Type().Field(i).Name + dm.fieldNameMap.Store(mapFieldName, realFieldName) + } + } + + // store register flag + dm.registerMap.Store(typeName, nil) + return nil +} + +// GetFieldName get fieldName with ElemValue and index +// if config tag string, return tag value +func (dm *mapperObject) getFieldName(objElem reflect.Value, index int) string { + fieldName := "" + field := objElem.Type().Field(index) + tag := dm.getStructTag(field) + if tag != "" { + fieldName = tag + } else { + fieldName = field.Name + } + return fieldName +} + +// UseWrapper register a type wrapper +func (dm *mapperObject) useWrapper(w TypeWrapper) { + if len(dm.typeWrappers) > 0 { + dm.typeWrappers[len(dm.typeWrappers)-1].SetNext(w) + } + dm.typeWrappers = append(dm.typeWrappers, w) +} + +func (dm *mapperObject) elemMapper(fromElem, toElem reflect.Value) error { + // check register flag + // if not register, register it + if !dm.checkIsRegister(fromElem) { + if err := dm.registerValue(fromElem); err != nil { + return err + } + } + if !dm.checkIsRegister(toElem) { + if err := dm.registerValue(toElem); err != nil { + return err + } + } + if toElem.Type().Kind() == reflect.Map { + dm.elemToMap(fromElem, toElem) + } else { + dm.elemToStruct(fromElem, toElem) + } + + return nil +} + +func (dm *mapperObject) elemToStruct(fromElem, toElem reflect.Value) { + for i := 0; i < fromElem.NumField(); i++ { + fromFieldInfo := fromElem.Field(i) + fieldName := dm.getFieldName(fromElem, i) + // check field is exists + realFieldName, exists := dm.CheckExistsField(toElem, fieldName) + if !exists { + continue + } + + toFieldInfo := toElem.FieldByName(realFieldName) + // check field is same type + if dm.enabledTypeChecking { + if fromFieldInfo.Kind() != toFieldInfo.Kind() { + continue + } + } + + if dm.enabledMapperStructField && + toFieldInfo.Kind() == reflect.Struct && fromFieldInfo.Kind() == reflect.Struct && + toFieldInfo.Type() != fromFieldInfo.Type() && + !dm.checkIsTypeWrapper(toFieldInfo) && !dm.checkIsTypeWrapper(fromFieldInfo) { + x := reflect.New(toFieldInfo.Type()).Elem() + err := dm.elemMapper(fromFieldInfo, x) + if err != nil { + fmt.Println("auto mapper field", fromFieldInfo, "=>", toFieldInfo, "error", err.Error()) + } else { + toFieldInfo.Set(x) + } + } else { + isSet := false + if dm.enabledAutoTypeConvert { + if dm.DefaultTimeWrapper.IsType(fromFieldInfo) && toFieldInfo.Kind() == reflect.Int64 { + fromTime := fromFieldInfo.Interface().(time.Time) + toFieldInfo.Set(reflect.ValueOf(TimeToUnix(fromTime))) + isSet = true + } else if dm.DefaultTimeWrapper.IsType(toFieldInfo) && fromFieldInfo.Kind() == reflect.Int64 { + fromTime := fromFieldInfo.Interface().(int64) + toFieldInfo.Set(reflect.ValueOf(UnixToTime(fromTime))) + isSet = true + } + } + if !isSet { + toFieldInfo.Set(fromFieldInfo) + } + } + + } +} + +func (dm *mapperObject) elemToMap(fromElem, toElem reflect.Value) { + for i := 0; i < fromElem.NumField(); i++ { + fromFieldInfo := fromElem.Field(i) + fieldName := dm.getFieldName(fromElem, i) + toElem.SetMapIndex(reflect.ValueOf(fieldName), fromFieldInfo) + } +} + +func (dm *mapperObject) setFieldValue(fieldValue reflect.Value, fieldKind reflect.Kind, value interface{}) error { + switch fieldKind { + case reflect.Bool: + if value == nil { + fieldValue.SetBool(false) + } else if v, ok := value.(bool); ok { + fieldValue.SetBool(v) + } else { + v, _ := Convert(ToString(value)).Bool() + fieldValue.SetBool(v) + } + + case reflect.String: + if value == nil { + fieldValue.SetString("") + } else { + fieldValue.SetString(ToString(value)) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if value == nil { + fieldValue.SetInt(0) + } else { + val := reflect.ValueOf(value) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fieldValue.SetInt(val.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + fieldValue.SetInt(int64(val.Uint())) + default: + v, _ := Convert(ToString(value)).Int64() + fieldValue.SetInt(v) + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if value == nil { + fieldValue.SetUint(0) + } else { + val := reflect.ValueOf(value) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fieldValue.SetUint(uint64(val.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + fieldValue.SetUint(val.Uint()) + default: + v, _ := Convert(ToString(value)).Uint64() + fieldValue.SetUint(v) + } + } + case reflect.Float64, reflect.Float32: + if value == nil { + fieldValue.SetFloat(0) + } else { + val := reflect.ValueOf(value) + switch val.Kind() { + case reflect.Float64: + fieldValue.SetFloat(val.Float()) + default: + v, _ := Convert(ToString(value)).Float64() + fieldValue.SetFloat(v) + } + } + case reflect.Struct: + if value == nil { + fieldValue.Set(reflect.Zero(fieldValue.Type())) + } else if dm.DefaultTimeWrapper.IsType(fieldValue) { + var timeString string + if fieldValue.Type() == dm.timeType { + timeString = "" + fieldValue.Set(reflect.ValueOf(value)) + } + if fieldValue.Type() == dm.jsonTimeType { + timeString = "" + fieldValue.Set(reflect.ValueOf(JSONTime(value.(time.Time)))) + } + switch d := value.(type) { + case []byte: + timeString = string(d) + case string: + timeString = d + case int64: + if dm.enabledAutoTypeConvert { + // try to transform Unix time to local Time + t, err := UnixToTimeLocation(value.(int64), time.UTC.String()) + if err != nil { + return err + } + fieldValue.Set(reflect.ValueOf(t)) + } + } + if timeString != "" { + if len(timeString) >= 19 { + // 满足yyyy-MM-dd HH:mm:ss格式 + timeString = timeString[:19] + t, err := time.ParseInLocation(formatDateTime, timeString, time.UTC) + if err == nil { + t = t.In(time.UTC) + fieldValue.Set(reflect.ValueOf(t)) + } + } else if len(timeString) >= 10 { + // 满足yyyy-MM-dd格式 + timeString = timeString[:10] + t, err := time.ParseInLocation(formatDate, timeString, time.UTC) + if err == nil { + fieldValue.Set(reflect.ValueOf(t)) + } + } + } + } + default: + if reflect.ValueOf(value).Type() == fieldValue.Type() { + fieldValue.Set(reflect.ValueOf(value)) + } + } + + return nil +} + +func (dm *mapperObject) getStructTag(field reflect.StructField) string { + tagValue := "" + // 1.check mapperTagKey + if dm.enabledMapperTag { + tagValue = field.Tag.Get(mapperTagKey) + if dm.checkTagValidity(tagValue) { + return tagValue + } + } + + // 2.check jsonTagKey + if dm.enabledJsonTag { + tagValue = field.Tag.Get(jsonTagKey) + if dm.checkTagValidity(tagValue) { + // support more tag property, as json tag omitempty 2018-07-13 + return strings.Split(tagValue, ",")[0] + } + } + + return "" +} + +func (dm *mapperObject) checkTagValidity(tagValue string) bool { + if tagValue != "" && tagValue != IgnoreTagValue { + return true + } + return false +} + +func (dm *mapperObject) checkIsRegister(objElem reflect.Value) bool { + typeName := objElem.Type().String() + _, isOk := dm.registerMap.Load(typeName) + return isOk +} + +// convert slice interface{} to []interface{} +func (dm *mapperObject) convertToSlice(arr interface{}) []interface{} { + v := reflect.ValueOf(arr) + if v.Kind() == reflect.Ptr { + if v.Elem().Kind() != reflect.Slice { + panic("fromSlice arr is not a pointer to a slice") + } + v = v.Elem() + } else { + if v.Kind() != reflect.Slice { + panic("fromSlice arr is not a slice") + } + } + l := v.Len() + ret := make([]interface{}, l) + for i := 0; i < l; i++ { + ret[i] = v.Index(i).Interface() + } + return ret +} + +// CheckIsTypeWrapper check value is in type wrappers +func (dm *mapperObject) checkIsTypeWrapper(value reflect.Value) bool { + for _, w := range dm.typeWrappers { + if w.IsType(value) { + return true + } + } + return false +} diff --git a/mapper_object_test.go b/mapper_object_test.go new file mode 100644 index 0000000..fac2bc5 --- /dev/null +++ b/mapper_object_test.go @@ -0,0 +1,465 @@ +package mapper + +import ( + "reflect" + "strconv" + "sync" + "testing" + "time" +) + +func Test_Object_SetEnabledTypeChecking(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + if m.IsEnabledTypeChecking() != true { + t.Error("SetEnabledTypeChecking error: set true but query is not true") + } else { + t.Log("SetEnabledTypeChecking success") + } +} + +func Test_ObjectGetTypeName(t *testing.T) { + m := NewMapper() + name := m.GetTypeName(&testStruct{}) + if name == "" { + t.Error("RunResult error: name is empty") + } else { + t.Log("RunResult success:", name) + } +} + +func BenchmarkObjectGetTypeName(b *testing.B) { + m := NewMapper() + for i := 0; i < b.N; i++ { + m.GetTypeName(&testStruct{}) + } +} + +func Test_Object_GetFieldNameFromMapperTag(t *testing.T) { + m := NewMapper() + v := TagStruct{} + fieldName := m.GetFieldName(reflect.ValueOf(v), 0) + if fieldName == "UserName" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } +} + +func Test_Object_GetFieldNameFromJsonTag(t *testing.T) { + m := NewMapper() + v := TagStruct{} + fieldName := m.GetFieldName(reflect.ValueOf(v), 1) + if fieldName == "UserSex" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } +} + +func Test_Object_SetEnableMapperTag(t *testing.T) { + m := NewMapper() + v := TagStruct{} + m.SetEnabledMapperTag(false) + fieldName := m.GetFieldName(reflect.ValueOf(v), 0) + if fieldName == "Name" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } + m.SetEnabledMapperTag(true) + fieldName = m.GetFieldName(reflect.ValueOf(v), 0) + if fieldName == "UserName" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } +} + +func Test_Object_SetEnableJsonTag(t *testing.T) { + m := NewMapper() + v := TagStruct{} + m.SetEnabledJsonTag(false) + fieldName := m.GetFieldName(reflect.ValueOf(v), 1) + if fieldName == "Sex" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } + m.SetEnabledJsonTag(true) + fieldName = m.GetFieldName(reflect.ValueOf(v), 1) + if fieldName == "UserSex" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } +} + +func Test_Object_GetFieldNameWithElem(t *testing.T) { + m := NewMapper() + fieldName := m.GetFieldName(testValue.Elem(), 0) + if fieldName == "Name" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } +} + +func BenchmarkObjectGetFieldNameWithElem(b *testing.B) { + m := NewMapper() + for i := 0; i < b.N; i++ { + m.GetFieldName(testValue.Elem(), 0) + } +} + +func Test_Object_CheckExistsField(t *testing.T) { + m := NewMapper() + m.Register(&testStruct{}) + fieldName := "Name" + _, isOk := m.CheckExistsField(testValue.Elem(), fieldName) + if isOk { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not exists", fieldName) + } +} + +func BenchmarkObjectCheckExistsField(b *testing.B) { + m := NewMapper() + m.Register(&testStruct{}) + elem := testValue.Elem() + fieldName := "Name" + for i := 0; i < b.N; i++ { + m.CheckExistsField(elem, fieldName) + } +} + +func Test_Object_Mapper(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := &ToStruct{} + err := m.Mapper(from, to) + if err != nil { + t.Error("RunResult error: mapper error", err) + } else { + t.Log("RunResult success:", to) + } +} + +func Test_Object_MapperSlice(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + var fromSlice []*FromStruct + var toSlice []*ToStruct + for i := 0; i < 10; i++ { + fromSlice = append(fromSlice, &FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) + } + err := m.MapperSlice(fromSlice, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + for i := 0; i < len(fromSlice); i++ { + if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || + !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || + !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { + t.Fail() + } + } + } +} + +func Test_Object_MapperSlice2(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + var fromSlice []*FromStruct + var toSlice []*ToStruct + for i := 0; i < 10; i++ { + fromSlice = append(fromSlice, &FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) + } + err := m.MapperSlice(&fromSlice, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + for i := 0; i < len(fromSlice); i++ { + if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || + !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || + !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { + t.Fail() + } + } + } +} + +func Test_Object_MapperStructSlice(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + var fromSlice []FromStruct + var toSlice []ToStruct + for i := 0; i < 10; i++ { + fromSlice = append(fromSlice, FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) + } + err := m.MapperSlice(fromSlice, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + for i := 0; i < len(fromSlice); i++ { + if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || + !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || + !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { + t.Fail() + } + } + } +} + +func Test_Object_MapperStructSlice2(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + var fromSlice []FromStruct + var toSlice []ToStruct + for i := 0; i < 10; i++ { + fromSlice = append(fromSlice, FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) + } + err := m.MapperSlice(&fromSlice, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + for i := 0; i < len(fromSlice); i++ { + if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || + !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || + !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { + t.Fail() + } + } + } +} + +func BenchmarkObjectMapperSlice(b *testing.B) { + m := NewMapper() + var fromSlice []*FromStruct + var toSlice []*ToStruct + for i := 0; i < 10; i++ { + fromSlice = append(fromSlice, &FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) + } + for i := 0; i < b.N; i++ { + m.MapperSlice(fromSlice, &toSlice) + } +} + +func Test_Object_AutoMapper(t *testing.T) { + m := NewMapper() + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := &ToStruct{} + err := m.AutoMapper(from, to) + if err != nil { + t.Error("RunResult error: mapper error", err) + } else { + t.Log("RunResult success:", to) + } +} + +func Test_Object_AutoMapper_StructToMap(t *testing.T) { + m := NewMapper() + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := make(map[string]interface{}) + err := m.AutoMapper(from, &to) + if err != nil { + t.Error("RunResult error: mapper error", err) + } else { + if to["UserName"] == "From" { + t.Log("RunResult success:", to) + } else { + t.Error("RunResult failed: map[UserName]", to["UserName"]) + } + } +} + +func Test_Object_MapperMap(t *testing.T) { + m := NewMapper() + validateTime, _ := time.Parse("2006-01-02 15:04:05", "2017-01-01 10:00:00") + fromMap := make(map[string]interface{}) + fromMap["Name"] = "test" + fromMap["Sex"] = true + fromMap["Age"] = 10 + fromMap["Time"] = validateTime + fromMap["Time2"] = validateTime + toObj := &testStruct{} + err := m.MapperMap(fromMap, toObj) + if err != nil && toObj.Time != validateTime { + t.Error("RunResult error: mapper error", err) + } else { + t.Log("RunResult success:", toObj) + } +} + +func Test_Object_MapToSlice(t *testing.T) { + m := NewMapper() + var toSlice []*testStruct + fromMaps := make(map[string]interface{}) + for i := 0; i < 10; i++ { + fromMap := make(map[string]interface{}) + fromMap["Name"] = "s" + strconv.Itoa(i) + fromMap["Sex"] = true + fromMap["Age"] = i + fromMaps[strconv.Itoa(i)] = fromMap + } + err := m.MapToSlice(fromMaps, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + } +} + +func Test_Object_MapperMapSlice(t *testing.T) { + m := NewMapper() + var toSlice []*testStruct + fromMaps := make(map[string]map[string]interface{}) + for i := 0; i < 10; i++ { + fromMap := make(map[string]interface{}) + fromMap["Name"] = "s" + strconv.Itoa(i) + fromMap["Sex"] = true + fromMap["Age"] = i + fromMaps[strconv.Itoa(i)] = fromMap + } + err := m.MapperMapSlice(fromMaps, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + } +} + +func Test_Object_MapperStructMapSlice(t *testing.T) { + m := NewMapper() + var toSlice []testStruct + fromMaps := make(map[string]map[string]interface{}) + for i := 0; i < 10; i++ { + fromMap := make(map[string]interface{}) + fromMap["Name"] = "s" + strconv.Itoa(i) + fromMap["Sex"] = true + fromMap["Age"] = i + fromMaps[strconv.Itoa(i)] = fromMap + } + err := m.MapperMapSlice(fromMaps, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + } + +} + +func Test_Object_IsTimeField(t *testing.T) { + m := NewMapper() + t1 := time.Now() + if m.GetDefaultTimeWrapper().IsType(reflect.ValueOf(t1)) { + t.Log("check time.Now ok") + } else { + t.Error("check time.Now error") + } + + var t2 JSONTime + t2 = JSONTime(time.Now()) + if m.GetDefaultTimeWrapper().IsType(reflect.ValueOf(t2)) { + t.Log("check mapper.Time ok") + } else { + t.Error("check mapper.Time error") + } +} + +func Test_Object_MapToJson_JsonToMap(t *testing.T) { + m := NewMapper() + fromMap := createMap() + data, err := m.MapToJson(fromMap) + if err != nil { + t.Error("MapToJson error", err) + } else { + var retMap map[string]interface{} + err = m.JsonToMap(data, &retMap) + if err != nil { + t.Error("MapToJson.JsonToMap error", err) + } + if len(retMap) != len(fromMap) { + t.Error("MapToJson failed, not match length") + } + t.Log("MapToJson success", fromMap, retMap) + } +} + +func BenchmarkObjectMapperMapSlice(b *testing.B) { + m := NewMapper() + var s []*testStruct + fromMaps := make(map[string]map[string]interface{}) + for i := 0; i < 10; i++ { + fromMap := make(map[string]interface{}) + fromMap["Name"] = "s" + strconv.Itoa(i) + fromMap["Sex"] = true + fromMap["Age"] = i + fromMaps[strconv.Itoa(i)] = fromMap + } + for i := 0; i < b.N; i++ { + m.MapperMapSlice(fromMaps, s) + } +} + +func BenchmarkObjectMapper(b *testing.B) { + m := NewMapper() + m.Register(&FromStruct{}) + m.Register(&ToStruct{}) + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := &ToStruct{} + + for i := 0; i < b.N; i++ { + m.Mapper(from, to) + } +} + +func BenchmarkObjectAutoMapper(b *testing.B) { + m := NewMapper() + m.Register(&FromStruct{}) + m.Register(&ToStruct{}) + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := &ToStruct{} + + for i := 0; i < b.N; i++ { + m.Mapper(from, to) + } +} + +func BenchmarkObjectAutoMapper_Map(b *testing.B) { + m := NewMapper() + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := make(map[string]interface{}) + + for i := 0; i < b.N; i++ { + m.Mapper(from, &to) + } +} + +func BenchmarkObjectMapperMap(b *testing.B) { + m := NewMapper() + m.Register(&testStruct{}) + fromMap := make(map[string]interface{}) + fromMap["Name"] = "test" + fromMap["Sex"] = true + fromMap["Age"] = 10 + fromMap["time"] = time.Now() + toObj := &testStruct{} + + for i := 0; i < b.N; i++ { + m.MapperMap(fromMap, toObj) + } +} + +func BenchmarkObjectSyncMap(b *testing.B) { + var sMap sync.Map + for i := 0; i < b.N; i++ { + sMap.Load("1") + } +} diff --git a/version.md b/version.md index ce085ac..648b161 100644 --- a/version.md +++ b/version.md @@ -1,5 +1,55 @@ ## devfeel/mapper +#### Version 0.7.7 +* Feature: add Object-oriented interface for the mapper. +* comment: the old version implementation will be refactored in next release. +* About the new feature:: +``` + package main + +import ( + "fmt" + "time" + + "github.com/devfeel/mapper" +) +type ( + User struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"age"` + } + + Student struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"-"` + } +) + +func main() { + user := &User{Name: "test", Age: 10} + student := &Student{} + + // create mapper object + m := mapper.NewMapper() + + // enable the type checking + m.SetEnabledTypeChecking(true) + + student.Age = 1 + + // disable the json tag + m.SetEnabledJsonTag(false) + + // student::age should be 1 + m.Mapper(user, student) + + fmt.Println(student) +} +``` + +* Tips: Thanks to @aeramu for issue #12 +* 2021-10-19 12:00 in ShangHai +* #### Version 0.7.6 * Feature: add SetEnabledMapperTag to set enabled flag for 'Mapper' tag check * Feature: add SetEnabledJsonTag to set enabled flag for 'Json' tag check From ed6bc86dc12b31831d39fa43dfe1000dfef235d8 Mon Sep 17 00:00:00 2001 From: shyandsy Date: Fri, 15 Apr 2022 23:40:42 +0800 Subject: [PATCH 2/3] Revert "feature: object oriented api for the mapper" --- README.md | 70 +----- constant.go | 12 - convert.go | 5 +- example/object/go.mod | 7 - example/object/go.sum | 0 example/object/main.go | 39 --- example/typewrapper/main.go | 18 +- go.mod | 5 - go.sum | 2 - mapper.go | 52 ++-- mapper_object.go | 341 -------------------------- mapper_object_internal.go | 319 ------------------------- mapper_object_test.go | 465 ------------------------------------ version.md | 50 ---- 14 files changed, 29 insertions(+), 1356 deletions(-) delete mode 100644 constant.go delete mode 100644 example/object/go.mod delete mode 100644 example/object/go.sum delete mode 100644 example/object/main.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 mapper_object.go delete mode 100644 mapper_object_internal.go delete mode 100644 mapper_object_test.go diff --git a/README.md b/README.md index 9eb4bda..f78561e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # devfeel/mapper - A simple and easy go tools for auto mapper struct to map, struct to struct, slice to slice, map to slice, map to json. ## 1. Install @@ -9,8 +8,6 @@ go get -u github.com/devfeel/mapper ``` ## 2. Getting Started - -Traditional Usage ```go package main @@ -69,8 +66,8 @@ func main() { fmt.Println("teacher", teacher) fmt.Println("userMap:", userMap) } -``` +``` 执行main,输出: ``` student: &{test 10 testId 100} @@ -79,59 +76,7 @@ teacher &{test 10 testId } userMap: &{map 10 x1asd 100 2017-11-20 13:45:56.3972504 +0800 CST m=+0.006004001} ``` -Object Usage - -```go -package main - -import ( - "fmt" - "github.com/devfeel/mapper" -) - -type ( - User struct { - Name string `json:"name" mapper:"name"` - Age int `json:"age" mapper:"age"` - } - - Student struct { - Name string `json:"name" mapper:"name"` - Age int `json:"age" mapper:"-"` - } -) - -func main() { - user := &User{Name: "test", Age: 10} - student := &Student{} - - // create mapper object - m := mapper.NewMapper() - - // enable the type checking - m.SetEnabledTypeChecking(true) - - student.Age = 1 - - // disable the json tag - m.SetEnabledJsonTag(false) - - // student::age should be 1 - m.Mapper(user, student) - - fmt.Println(student) -} -``` - -执行main,输出: -``` -&{test 1} -``` - - - ## Features - * 支持不同结构体相同名称相同类型字段自动赋值,使用Mapper * 支持不同结构体Slice的自动赋值,使用MapperSlice * 支持字段为结构体时的自动赋值 @@ -143,15 +88,4 @@ func main() { * 支持tag标签,tag关键字为 mapper * 兼容json-tag标签 * 当tag为"-"时,将忽略tag定义,使用struct field name -* 无需手动Register struct,内部自动识别 -* 支持开启关闭 - * SetEnabledTypeChecking(bool) // 类型检查 - * IsEnabledTypeChecking - * SetEnabledMapperTag // mapper tag - * IsEnabledMapperTag - * SetEnabledJsonTag // json tag - * IsEnabledJsonTag - * SetEnabledAutoTypeConvert // auto type convert - * IsEnabledAutoTypeConvert - * SetEnabledMapperStructField // mapper struct field - * IsEnabledMapperStructField \ No newline at end of file +* 无需手动Register struct,内部自动识别 \ No newline at end of file diff --git a/constant.go b/constant.go deleted file mode 100644 index 587fd79..0000000 --- a/constant.go +++ /dev/null @@ -1,12 +0,0 @@ -package mapper - -const ( - packageVersion = "0.7.6" - mapperTagKey = "mapper" - jsonTagKey = "json" - IgnoreTagValue = "-" - nameConnector = "_" - formatTime = "15:04:05" - formatDate = "2006-01-02" - formatDateTime = "2006-01-02 15:04:05" -) \ No newline at end of file diff --git a/convert.go b/convert.go index c1e34ab..b7619da 100644 --- a/convert.go +++ b/convert.go @@ -1,7 +1,6 @@ package mapper import ( - "encoding/hex" "fmt" "math/big" "reflect" @@ -23,12 +22,12 @@ func (f *Convert) Set(v string) { // Clear string func (f *Convert) Clear() { - *f = Convert(hex.EncodeToString([]byte{0x1E})) + *f = Convert(0x1E) } // Exist check string exist func (f Convert) Exist() bool { - return string(f) != hex.EncodeToString([]byte{0x1E}) + return string(f) != string(0x1E) } // Bool string to bool diff --git a/example/object/go.mod b/example/object/go.mod deleted file mode 100644 index 6734ac5..0000000 --- a/example/object/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module object - -go 1.16 - -replace github.com/devfeel/mapper v0.7.6 => ./../../../mapper - -require github.com/devfeel/mapper v0.7.6 diff --git a/example/object/go.sum b/example/object/go.sum deleted file mode 100644 index e69de29..0000000 diff --git a/example/object/main.go b/example/object/main.go deleted file mode 100644 index 583d788..0000000 --- a/example/object/main.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "fmt" - "github.com/devfeel/mapper" -) - -type ( - User struct { - Name string `json:"name" mapper:"name"` - Age int `json:"age" mapper:"age"` - } - - Student struct { - Name string `json:"name" mapper:"name"` - Age int `json:"age" mapper:"-"` - } -) - -func main() { - user := &User{Name: "test", Age: 10} - student := &Student{} - - // create mapper object - m := mapper.NewMapper() - - // enable the type checking - m.SetEnabledTypeChecking(true) - - student.Age = 1 - - // disable the json tag - m.SetEnabledJsonTag(false) - - // student::age should be 1 - m.Mapper(user, student) - - fmt.Println(student) -} diff --git a/example/typewrapper/main.go b/example/typewrapper/main.go index 696e27d..89148a6 100644 --- a/example/typewrapper/main.go +++ b/example/typewrapper/main.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + "github.com/devfeel/mapper" "reflect" "time" ) @@ -26,7 +28,7 @@ type decimal struct { } type DecimalWrapper struct { - // mapper.BaseTypeWrapper + mapper.BaseTypeWrapper } func (w *DecimalWrapper) IsType(value reflect.Value) bool { @@ -37,14 +39,12 @@ func (w *DecimalWrapper) IsType(value reflect.Value) bool { } func main() { - /* - mapper.UseWrapper(&DecimalWrapper{}) - user := &User{Name: "test", Age: 10, Score: decimal{value: 1}, Time: time.Now()} - stu := &Student{} + mapper.UseWrapper(&DecimalWrapper{}) + user := &User{Name: "test", Age: 10, Score: decimal{value: 1}, Time: time.Now()} + stu := &Student{} - mapper.AutoMapper(user, stu) + mapper.AutoMapper(user, stu) - fmt.Println(user) - fmt.Println(stu) - */ + fmt.Println(user) + fmt.Println(stu) } diff --git a/go.mod b/go.mod deleted file mode 100644 index 6ff406d..0000000 --- a/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module mapper - -go 1.16 - -require github.com/devfeel/mapper v0.7.6 diff --git a/go.sum b/go.sum deleted file mode 100644 index 1c15dea..0000000 --- a/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/devfeel/mapper v0.7.6 h1:FItOzW4YpEp1x+U1izauW5+sbVIiymOwF7AiWxIdUVk= -github.com/devfeel/mapper v0.7.6/go.mod h1:foz4u16jrssGoDfnWYQGFcthjlU6uBV5UV8uYJfKneA= diff --git a/mapper.go b/mapper.go index 0bb7d5a..ee19c01 100644 --- a/mapper.go +++ b/mapper.go @@ -23,36 +23,16 @@ var ( typeWrappers []TypeWrapper ) -type IMapper interface { - Mapper(fromObj, toObj interface{}) error - AutoMapper(fromObj, toObj interface{}) error - MapperMap(fromMap map[string]interface{}, toObj interface{}) error - Register(obj interface{}) error - GetTypeName(obj interface{}) string - GetFieldName(objElem reflect.Value, index int) string - GetDefaultTimeWrapper() *TimeWrapper - CheckExistsField(elem reflect.Value, fieldName string) (realFieldName string, exists bool) - MapToSlice(fromMap map[string]interface{}, toSlice interface{}) error - MapperMapSlice(fromMaps map[string]map[string]interface{}, toSlice interface{}) error - MapperSlice(fromSlice, toSlice interface{}) error - MapToJson(fromMap map[string]interface{}) ([]byte, error) - JsonToMap(body []byte, toMap *map[string]interface{}) error - - SetEnabledTypeChecking(isEnabled bool) - IsEnabledTypeChecking() bool - - SetEnabledMapperTag(isEnabled bool) - IsEnabledMapperTag() bool - - SetEnabledJsonTag(isEnabled bool) - IsEnabledJsonTag() bool - - SetEnabledAutoTypeConvert(isEnabled bool) - IsEnabledAutoTypeConvert() bool - - SetEnabledMapperStructField(isEnabled bool) - IsEnabledMapperStructField() bool -} +const ( + packageVersion = "0.7.6" + mapperTagKey = "mapper" + jsonTagKey = "json" + IgnoreTagValue = "-" + nameConnector = "_" + formatTime = "15:04:05" + formatDate = "2006-01-02" + formatDateTime = "2006-01-02 15:04:05" +) func init() { ZeroValue = reflect.Value{} @@ -156,7 +136,7 @@ func registerValue(objValue reflect.Value) error { } } - // store register flag + //store register flag registerMap.Store(typeName, nil) return nil } @@ -214,14 +194,14 @@ func MapperMap(fromMap map[string]interface{}, toObj interface{}) error { if toElem == ZeroValue { return errors.New("to obj is not legal value") } - // check register flag - // if not register, register it + //check register flag + //if not register, register it if !checkIsRegister(toElem) { Register(toObj) } for k, v := range fromMap { fieldName := k - // check field is exists + //check field is exists realFieldName, exists := CheckExistsField(toElem, fieldName) if !exists { continue @@ -294,7 +274,7 @@ func MapperMapSlice(fromMaps map[string]map[string]interface{}, toSlice interfac toElemType := reflect.TypeOf(toSlice).Elem().Elem() realType := toElemType.Kind() direct := reflect.Indirect(toValue) - // 3 elem parse: 1.[]*type 2.*type 3.type + //3 elem parse: 1.[]*type 2.*type 3.type if realType == reflect.Ptr { toElemType = toElemType.Elem() } @@ -327,7 +307,7 @@ func MapperSlice(fromSlice, toSlice interface{}) error { elemType := reflect.TypeOf(toSlice).Elem().Elem() realType := elemType.Kind() direct := reflect.Indirect(toValue) - // 3 elem parse: 1.[]*type 2.*type 3.type + //3 elem parse: 1.[]*type 2.*type 3.type if realType == reflect.Ptr { elemType = elemType.Elem() } diff --git a/mapper_object.go b/mapper_object.go deleted file mode 100644 index 54083ab..0000000 --- a/mapper_object.go +++ /dev/null @@ -1,341 +0,0 @@ -package mapper - -import ( - "encoding/json" - "errors" - "reflect" - "sync" - "time" -) - -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 -} - -func NewMapper() IMapper { - 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, - } - dm.useWrapper(dm.DefaultTimeWrapper) - return &dm -} - -// Mapper map and set value from struct fromObj to toObj -// not support auto register struct -func (dm *mapperObject) Mapper(fromObj, toObj interface{}) error { - fromElem := reflect.ValueOf(fromObj).Elem() - toElem := reflect.ValueOf(toObj).Elem() - if fromElem == dm.ZeroValue { - return errors.New("from obj is not legal value") - } - if toElem == dm.ZeroValue { - return errors.New("to obj is not legal value") - } - return dm.elemMapper(fromElem, toElem) -} - -// AutoMapper mapper and set value from struct fromObj to toObj -// support auto register struct -func (dm *mapperObject) AutoMapper(fromObj, toObj interface{}) error { - return dm.Mapper(fromObj, toObj) -} - -// MapperMap mapper and set value from map to object -// support auto register struct -// now support field type: -// 1.reflect.Bool -// 2.reflect.String -// 3.reflect.Int8\16\32\64 -// 4.reflect.Uint8\16\32\64 -// 5.reflect.Float32\64 -// 6.time.Time -func (dm *mapperObject) MapperMap(fromMap map[string]interface{}, toObj interface{}) error { - toElemType := reflect.ValueOf(toObj) - toElem := toElemType - if toElemType.Kind() == reflect.Ptr { - toElem = toElemType.Elem() - } - - if toElem == dm.ZeroValue { - return errors.New("to obj is not legal value") - } - // check register flag - // if not register, register it - if !dm.checkIsRegister(toElem) { - if err := dm.Register(toObj); err != nil { - return err - } - } - for k, v := range fromMap { - fieldName := k - // check field is exists - realFieldName, exists := dm.CheckExistsField(toElem, fieldName) - if !exists { - continue - } - fieldInfo, exists := toElem.Type().FieldByName(realFieldName) - if !exists { - continue - } - - fieldKind := fieldInfo.Type.Kind() - fieldValue := toElem.FieldByName(realFieldName) - - if err := dm.setFieldValue(fieldValue, fieldKind, v); err != nil { - return err - } - } - return nil -} - -// SetEnabledTypeChecking set enabled flag for TypeChecking -// 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 -} - -func (dm *mapperObject) IsEnabledTypeChecking() bool { - return dm.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 -} - -func (dm *mapperObject) IsEnabledMapperTag() bool { - return dm.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 -} - -func (dm *mapperObject) IsEnabledJsonTag() bool { - return dm.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 -} - -func (dm *mapperObject) IsEnabledAutoTypeConvert() bool { - return dm.enabledAutoTypeConvert -} - -// SetEnabledMapperStructField set enabled flag for MapperStructField -// if set true, the reflect.Struct field will auto mapper -// must follow premises: -// 1. fromField and toField type must be reflect.Struct and not time.Time -// 2. fromField and toField must be not same type -// default is enabled -func (dm *mapperObject) SetEnabledMapperStructField(isEnabled bool) { - dm.enabledMapperStructField = isEnabled -} - -func (dm *mapperObject) IsEnabledMapperStructField() bool { - return dm.enabledMapperStructField -} - -// GetTypeName get type name -func (dm *mapperObject) GetTypeName(obj interface{}) string { - object := reflect.ValueOf(obj) - return object.String() -} - -// GetFieldName get fieldName with ElemValue and index -// if config tag string, return tag value -func (dm *mapperObject) GetFieldName(objElem reflect.Value, index int) string { - fieldName := "" - field := objElem.Type().Field(index) - tag := dm.getStructTag(field) - if tag != "" { - fieldName = tag - } else { - fieldName = field.Name - } - return fieldName -} - -func (dm *mapperObject) GetDefaultTimeWrapper() *TimeWrapper { - return dm.DefaultTimeWrapper -} - -// Register register struct to init Map -func (dm *mapperObject) Register(obj interface{}) error { - objValue := reflect.ValueOf(obj) - if objValue == dm.ZeroValue { - return errors.New("obj value does not exist") - } - return dm.registerValue(objValue) -} - -// MapToSlice mapper from map[string]interface{} to a slice of any type's ptr -// toSlice must be a slice of any type. -func (dm *mapperObject) MapToSlice(fromMap map[string]interface{}, toSlice interface{}) error { - var err error - toValue := reflect.ValueOf(toSlice) - if toValue.Kind() != reflect.Ptr { - return errors.New("toSlice must be a pointer to a slice") - } - if toValue.IsNil() { - return errors.New("toSlice must not be a nil pointer") - } - - toElemType := reflect.TypeOf(toSlice).Elem().Elem() - realType := toElemType.Kind() - direct := reflect.Indirect(toValue) - if realType == reflect.Ptr { - toElemType = toElemType.Elem() - } - for _, v := range fromMap { - if reflect.TypeOf(v).Kind().String() == "map" { - elem := reflect.New(toElemType) - err = dm.MapperMap(v.(map[string]interface{}), elem.Interface()) - if err == nil { - if realType == reflect.Ptr { - direct.Set(reflect.Append(direct, elem)) - } else { - direct.Set(reflect.Append(direct, elem).Elem()) - } - } - } else { - if realType == reflect.Ptr { - direct.Set(reflect.Append(direct, reflect.ValueOf(v))) - } else { - direct.Set(reflect.Append(direct, reflect.ValueOf(v).Elem())) - } - } - - } - return err -} - -// MapperMapSlice mapper from map[string]map[string]interface{} to a slice of any type's ptr -// toSlice must be a slice of any type. -// Deprecated: will remove on v1.0, please use MapToSlice instead -func (dm *mapperObject) MapperMapSlice(fromMaps map[string]map[string]interface{}, toSlice interface{}) error { - var err error - toValue := reflect.ValueOf(toSlice) - if toValue.Kind() != reflect.Ptr { - return errors.New("toSlice must be a pointer to a slice") - } - if toValue.IsNil() { - return errors.New("toSlice must not be a nil pointer") - } - - toElemType := reflect.TypeOf(toSlice).Elem().Elem() - realType := toElemType.Kind() - direct := reflect.Indirect(toValue) - // 3 elem parse: 1.[]*type 2.*type 3.type - if realType == reflect.Ptr { - toElemType = toElemType.Elem() - } - for _, v := range fromMaps { - elem := reflect.New(toElemType) - err = dm.MapperMap(v, elem.Interface()) - if err == nil { - if realType == reflect.Ptr { - direct.Set(reflect.Append(direct, elem)) - } else { - direct.Set(reflect.Append(direct, elem.Elem())) - } - } - } - return err -} - -// MapperSlice mapper from slice of struct to a slice of any type -// fromSlice and toSlice must be a slice of any type. -func (dm *mapperObject) MapperSlice(fromSlice, toSlice interface{}) error { - var err error - toValue := reflect.ValueOf(toSlice) - if toValue.Kind() != reflect.Ptr { - return errors.New("toSlice must be a pointer to a slice") - } - if toValue.IsNil() { - return errors.New("toSlice must not be a nil pointer") - } - - elemType := reflect.TypeOf(toSlice).Elem().Elem() - realType := elemType.Kind() - direct := reflect.Indirect(toValue) - // 3 elem parse: 1.[]*type 2.*type 3.type - if realType == reflect.Ptr { - elemType = elemType.Elem() - } - - fromElems := dm.convertToSlice(fromSlice) - for _, v := range fromElems { - elem := reflect.New(elemType).Elem() - if realType == reflect.Ptr { - elem = reflect.New(elemType) - } - if realType == reflect.Ptr { - err = dm.elemMapper(reflect.ValueOf(v).Elem(), elem.Elem()) - } else { - err = dm.elemMapper(reflect.ValueOf(v), elem) - } - if err == nil { - direct.Set(reflect.Append(direct, elem)) - } - } - return err -} - -// MapToJson mapper from map[string]interface{} to json []byte -func (dm *mapperObject) MapToJson(fromMap map[string]interface{}) ([]byte, error) { - jsonStr, err := json.Marshal(fromMap) - if err != nil { - return nil, err - } - return jsonStr, nil -} - -// JsonToMap mapper from json []byte to map[string]interface{} -func (dm *mapperObject) JsonToMap(body []byte, toMap *map[string]interface{}) error { - err := json.Unmarshal(body, toMap) - return err -} - -// CheckExistsField check field is exists by name -func (dm *mapperObject) CheckExistsField(elem reflect.Value, fieldName string) (realFieldName string, exists bool) { - typeName := elem.Type().String() - fileKey := typeName + nameConnector + fieldName - realName, isOk := dm.fieldNameMap.Load(fileKey) - - if !isOk { - return "", isOk - } else { - return realName.(string), isOk - } -} diff --git a/mapper_object_internal.go b/mapper_object_internal.go deleted file mode 100644 index f9bc5fe..0000000 --- a/mapper_object_internal.go +++ /dev/null @@ -1,319 +0,0 @@ -package mapper - -import ( - "errors" - "fmt" - "reflect" - "strings" - "time" -) - -// registerValue register Value to init Map -func (dm *mapperObject) registerValue(objValue reflect.Value) error { - regValue := objValue - if objValue == dm.ZeroValue { - return errors.New("obj value does not exist") - } - - if regValue.Type().Kind() == reflect.Ptr { - regValue = regValue.Elem() - } - - typeName := regValue.Type().String() - if regValue.Type().Kind() == reflect.Struct { - for i := 0; i < regValue.NumField(); i++ { - mapFieldName := typeName + nameConnector + dm.getFieldName(regValue, i) - realFieldName := regValue.Type().Field(i).Name - dm.fieldNameMap.Store(mapFieldName, realFieldName) - } - } - - // store register flag - dm.registerMap.Store(typeName, nil) - return nil -} - -// GetFieldName get fieldName with ElemValue and index -// if config tag string, return tag value -func (dm *mapperObject) getFieldName(objElem reflect.Value, index int) string { - fieldName := "" - field := objElem.Type().Field(index) - tag := dm.getStructTag(field) - if tag != "" { - fieldName = tag - } else { - fieldName = field.Name - } - return fieldName -} - -// UseWrapper register a type wrapper -func (dm *mapperObject) useWrapper(w TypeWrapper) { - if len(dm.typeWrappers) > 0 { - dm.typeWrappers[len(dm.typeWrappers)-1].SetNext(w) - } - dm.typeWrappers = append(dm.typeWrappers, w) -} - -func (dm *mapperObject) elemMapper(fromElem, toElem reflect.Value) error { - // check register flag - // if not register, register it - if !dm.checkIsRegister(fromElem) { - if err := dm.registerValue(fromElem); err != nil { - return err - } - } - if !dm.checkIsRegister(toElem) { - if err := dm.registerValue(toElem); err != nil { - return err - } - } - if toElem.Type().Kind() == reflect.Map { - dm.elemToMap(fromElem, toElem) - } else { - dm.elemToStruct(fromElem, toElem) - } - - return nil -} - -func (dm *mapperObject) elemToStruct(fromElem, toElem reflect.Value) { - for i := 0; i < fromElem.NumField(); i++ { - fromFieldInfo := fromElem.Field(i) - fieldName := dm.getFieldName(fromElem, i) - // check field is exists - realFieldName, exists := dm.CheckExistsField(toElem, fieldName) - if !exists { - continue - } - - toFieldInfo := toElem.FieldByName(realFieldName) - // check field is same type - if dm.enabledTypeChecking { - if fromFieldInfo.Kind() != toFieldInfo.Kind() { - continue - } - } - - if dm.enabledMapperStructField && - toFieldInfo.Kind() == reflect.Struct && fromFieldInfo.Kind() == reflect.Struct && - toFieldInfo.Type() != fromFieldInfo.Type() && - !dm.checkIsTypeWrapper(toFieldInfo) && !dm.checkIsTypeWrapper(fromFieldInfo) { - x := reflect.New(toFieldInfo.Type()).Elem() - err := dm.elemMapper(fromFieldInfo, x) - if err != nil { - fmt.Println("auto mapper field", fromFieldInfo, "=>", toFieldInfo, "error", err.Error()) - } else { - toFieldInfo.Set(x) - } - } else { - isSet := false - if dm.enabledAutoTypeConvert { - if dm.DefaultTimeWrapper.IsType(fromFieldInfo) && toFieldInfo.Kind() == reflect.Int64 { - fromTime := fromFieldInfo.Interface().(time.Time) - toFieldInfo.Set(reflect.ValueOf(TimeToUnix(fromTime))) - isSet = true - } else if dm.DefaultTimeWrapper.IsType(toFieldInfo) && fromFieldInfo.Kind() == reflect.Int64 { - fromTime := fromFieldInfo.Interface().(int64) - toFieldInfo.Set(reflect.ValueOf(UnixToTime(fromTime))) - isSet = true - } - } - if !isSet { - toFieldInfo.Set(fromFieldInfo) - } - } - - } -} - -func (dm *mapperObject) elemToMap(fromElem, toElem reflect.Value) { - for i := 0; i < fromElem.NumField(); i++ { - fromFieldInfo := fromElem.Field(i) - fieldName := dm.getFieldName(fromElem, i) - toElem.SetMapIndex(reflect.ValueOf(fieldName), fromFieldInfo) - } -} - -func (dm *mapperObject) setFieldValue(fieldValue reflect.Value, fieldKind reflect.Kind, value interface{}) error { - switch fieldKind { - case reflect.Bool: - if value == nil { - fieldValue.SetBool(false) - } else if v, ok := value.(bool); ok { - fieldValue.SetBool(v) - } else { - v, _ := Convert(ToString(value)).Bool() - fieldValue.SetBool(v) - } - - case reflect.String: - if value == nil { - fieldValue.SetString("") - } else { - fieldValue.SetString(ToString(value)) - } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if value == nil { - fieldValue.SetInt(0) - } else { - val := reflect.ValueOf(value) - switch val.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - fieldValue.SetInt(val.Int()) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - fieldValue.SetInt(int64(val.Uint())) - default: - v, _ := Convert(ToString(value)).Int64() - fieldValue.SetInt(v) - } - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - if value == nil { - fieldValue.SetUint(0) - } else { - val := reflect.ValueOf(value) - switch val.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - fieldValue.SetUint(uint64(val.Int())) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - fieldValue.SetUint(val.Uint()) - default: - v, _ := Convert(ToString(value)).Uint64() - fieldValue.SetUint(v) - } - } - case reflect.Float64, reflect.Float32: - if value == nil { - fieldValue.SetFloat(0) - } else { - val := reflect.ValueOf(value) - switch val.Kind() { - case reflect.Float64: - fieldValue.SetFloat(val.Float()) - default: - v, _ := Convert(ToString(value)).Float64() - fieldValue.SetFloat(v) - } - } - case reflect.Struct: - if value == nil { - fieldValue.Set(reflect.Zero(fieldValue.Type())) - } else if dm.DefaultTimeWrapper.IsType(fieldValue) { - var timeString string - if fieldValue.Type() == dm.timeType { - timeString = "" - fieldValue.Set(reflect.ValueOf(value)) - } - if fieldValue.Type() == dm.jsonTimeType { - timeString = "" - fieldValue.Set(reflect.ValueOf(JSONTime(value.(time.Time)))) - } - switch d := value.(type) { - case []byte: - timeString = string(d) - case string: - timeString = d - case int64: - if dm.enabledAutoTypeConvert { - // try to transform Unix time to local Time - t, err := UnixToTimeLocation(value.(int64), time.UTC.String()) - if err != nil { - return err - } - fieldValue.Set(reflect.ValueOf(t)) - } - } - if timeString != "" { - if len(timeString) >= 19 { - // 满足yyyy-MM-dd HH:mm:ss格式 - timeString = timeString[:19] - t, err := time.ParseInLocation(formatDateTime, timeString, time.UTC) - if err == nil { - t = t.In(time.UTC) - fieldValue.Set(reflect.ValueOf(t)) - } - } else if len(timeString) >= 10 { - // 满足yyyy-MM-dd格式 - timeString = timeString[:10] - t, err := time.ParseInLocation(formatDate, timeString, time.UTC) - if err == nil { - fieldValue.Set(reflect.ValueOf(t)) - } - } - } - } - default: - if reflect.ValueOf(value).Type() == fieldValue.Type() { - fieldValue.Set(reflect.ValueOf(value)) - } - } - - return nil -} - -func (dm *mapperObject) getStructTag(field reflect.StructField) string { - tagValue := "" - // 1.check mapperTagKey - if dm.enabledMapperTag { - tagValue = field.Tag.Get(mapperTagKey) - if dm.checkTagValidity(tagValue) { - return tagValue - } - } - - // 2.check jsonTagKey - if dm.enabledJsonTag { - tagValue = field.Tag.Get(jsonTagKey) - if dm.checkTagValidity(tagValue) { - // support more tag property, as json tag omitempty 2018-07-13 - return strings.Split(tagValue, ",")[0] - } - } - - return "" -} - -func (dm *mapperObject) checkTagValidity(tagValue string) bool { - if tagValue != "" && tagValue != IgnoreTagValue { - return true - } - return false -} - -func (dm *mapperObject) checkIsRegister(objElem reflect.Value) bool { - typeName := objElem.Type().String() - _, isOk := dm.registerMap.Load(typeName) - return isOk -} - -// convert slice interface{} to []interface{} -func (dm *mapperObject) convertToSlice(arr interface{}) []interface{} { - v := reflect.ValueOf(arr) - if v.Kind() == reflect.Ptr { - if v.Elem().Kind() != reflect.Slice { - panic("fromSlice arr is not a pointer to a slice") - } - v = v.Elem() - } else { - if v.Kind() != reflect.Slice { - panic("fromSlice arr is not a slice") - } - } - l := v.Len() - ret := make([]interface{}, l) - for i := 0; i < l; i++ { - ret[i] = v.Index(i).Interface() - } - return ret -} - -// CheckIsTypeWrapper check value is in type wrappers -func (dm *mapperObject) checkIsTypeWrapper(value reflect.Value) bool { - for _, w := range dm.typeWrappers { - if w.IsType(value) { - return true - } - } - return false -} diff --git a/mapper_object_test.go b/mapper_object_test.go deleted file mode 100644 index fac2bc5..0000000 --- a/mapper_object_test.go +++ /dev/null @@ -1,465 +0,0 @@ -package mapper - -import ( - "reflect" - "strconv" - "sync" - "testing" - "time" -) - -func Test_Object_SetEnabledTypeChecking(t *testing.T) { - m := NewMapper() - m.SetEnabledTypeChecking(true) - if m.IsEnabledTypeChecking() != true { - t.Error("SetEnabledTypeChecking error: set true but query is not true") - } else { - t.Log("SetEnabledTypeChecking success") - } -} - -func Test_ObjectGetTypeName(t *testing.T) { - m := NewMapper() - name := m.GetTypeName(&testStruct{}) - if name == "" { - t.Error("RunResult error: name is empty") - } else { - t.Log("RunResult success:", name) - } -} - -func BenchmarkObjectGetTypeName(b *testing.B) { - m := NewMapper() - for i := 0; i < b.N; i++ { - m.GetTypeName(&testStruct{}) - } -} - -func Test_Object_GetFieldNameFromMapperTag(t *testing.T) { - m := NewMapper() - v := TagStruct{} - fieldName := m.GetFieldName(reflect.ValueOf(v), 0) - if fieldName == "UserName" { - t.Log("RunResult success:", fieldName) - } else { - t.Error("RunResult error: fieldName not match", fieldName) - } -} - -func Test_Object_GetFieldNameFromJsonTag(t *testing.T) { - m := NewMapper() - v := TagStruct{} - fieldName := m.GetFieldName(reflect.ValueOf(v), 1) - if fieldName == "UserSex" { - t.Log("RunResult success:", fieldName) - } else { - t.Error("RunResult error: fieldName not match", fieldName) - } -} - -func Test_Object_SetEnableMapperTag(t *testing.T) { - m := NewMapper() - v := TagStruct{} - m.SetEnabledMapperTag(false) - fieldName := m.GetFieldName(reflect.ValueOf(v), 0) - if fieldName == "Name" { - t.Log("RunResult success:", fieldName) - } else { - t.Error("RunResult error: fieldName not match", fieldName) - } - m.SetEnabledMapperTag(true) - fieldName = m.GetFieldName(reflect.ValueOf(v), 0) - if fieldName == "UserName" { - t.Log("RunResult success:", fieldName) - } else { - t.Error("RunResult error: fieldName not match", fieldName) - } -} - -func Test_Object_SetEnableJsonTag(t *testing.T) { - m := NewMapper() - v := TagStruct{} - m.SetEnabledJsonTag(false) - fieldName := m.GetFieldName(reflect.ValueOf(v), 1) - if fieldName == "Sex" { - t.Log("RunResult success:", fieldName) - } else { - t.Error("RunResult error: fieldName not match", fieldName) - } - m.SetEnabledJsonTag(true) - fieldName = m.GetFieldName(reflect.ValueOf(v), 1) - if fieldName == "UserSex" { - t.Log("RunResult success:", fieldName) - } else { - t.Error("RunResult error: fieldName not match", fieldName) - } -} - -func Test_Object_GetFieldNameWithElem(t *testing.T) { - m := NewMapper() - fieldName := m.GetFieldName(testValue.Elem(), 0) - if fieldName == "Name" { - t.Log("RunResult success:", fieldName) - } else { - t.Error("RunResult error: fieldName not match", fieldName) - } -} - -func BenchmarkObjectGetFieldNameWithElem(b *testing.B) { - m := NewMapper() - for i := 0; i < b.N; i++ { - m.GetFieldName(testValue.Elem(), 0) - } -} - -func Test_Object_CheckExistsField(t *testing.T) { - m := NewMapper() - m.Register(&testStruct{}) - fieldName := "Name" - _, isOk := m.CheckExistsField(testValue.Elem(), fieldName) - if isOk { - t.Log("RunResult success:", fieldName) - } else { - t.Error("RunResult error: fieldName not exists", fieldName) - } -} - -func BenchmarkObjectCheckExistsField(b *testing.B) { - m := NewMapper() - m.Register(&testStruct{}) - elem := testValue.Elem() - fieldName := "Name" - for i := 0; i < b.N; i++ { - m.CheckExistsField(elem, fieldName) - } -} - -func Test_Object_Mapper(t *testing.T) { - m := NewMapper() - m.SetEnabledTypeChecking(true) - from := &FromStruct{Name: "From", Sex: true, AA: "AA"} - to := &ToStruct{} - err := m.Mapper(from, to) - if err != nil { - t.Error("RunResult error: mapper error", err) - } else { - t.Log("RunResult success:", to) - } -} - -func Test_Object_MapperSlice(t *testing.T) { - m := NewMapper() - m.SetEnabledTypeChecking(true) - var fromSlice []*FromStruct - var toSlice []*ToStruct - for i := 0; i < 10; i++ { - fromSlice = append(fromSlice, &FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) - } - err := m.MapperSlice(fromSlice, &toSlice) - if err != nil { - t.Error(err) - } else { - t.Log(toSlice, len(toSlice)) - for i := 0; i < len(fromSlice); i++ { - if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || - !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || - !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { - t.Fail() - } - } - } -} - -func Test_Object_MapperSlice2(t *testing.T) { - m := NewMapper() - m.SetEnabledTypeChecking(true) - var fromSlice []*FromStruct - var toSlice []*ToStruct - for i := 0; i < 10; i++ { - fromSlice = append(fromSlice, &FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) - } - err := m.MapperSlice(&fromSlice, &toSlice) - if err != nil { - t.Error(err) - } else { - t.Log(toSlice, len(toSlice)) - for i := 0; i < len(fromSlice); i++ { - if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || - !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || - !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { - t.Fail() - } - } - } -} - -func Test_Object_MapperStructSlice(t *testing.T) { - m := NewMapper() - m.SetEnabledTypeChecking(true) - var fromSlice []FromStruct - var toSlice []ToStruct - for i := 0; i < 10; i++ { - fromSlice = append(fromSlice, FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) - } - err := m.MapperSlice(fromSlice, &toSlice) - if err != nil { - t.Error(err) - } else { - t.Log(toSlice, len(toSlice)) - for i := 0; i < len(fromSlice); i++ { - if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || - !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || - !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { - t.Fail() - } - } - } -} - -func Test_Object_MapperStructSlice2(t *testing.T) { - m := NewMapper() - m.SetEnabledTypeChecking(true) - var fromSlice []FromStruct - var toSlice []ToStruct - for i := 0; i < 10; i++ { - fromSlice = append(fromSlice, FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) - } - err := m.MapperSlice(&fromSlice, &toSlice) - if err != nil { - t.Error(err) - } else { - t.Log(toSlice, len(toSlice)) - for i := 0; i < len(fromSlice); i++ { - if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || - !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || - !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { - t.Fail() - } - } - } -} - -func BenchmarkObjectMapperSlice(b *testing.B) { - m := NewMapper() - var fromSlice []*FromStruct - var toSlice []*ToStruct - for i := 0; i < 10; i++ { - fromSlice = append(fromSlice, &FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) - } - for i := 0; i < b.N; i++ { - m.MapperSlice(fromSlice, &toSlice) - } -} - -func Test_Object_AutoMapper(t *testing.T) { - m := NewMapper() - from := &FromStruct{Name: "From", Sex: true, AA: "AA"} - to := &ToStruct{} - err := m.AutoMapper(from, to) - if err != nil { - t.Error("RunResult error: mapper error", err) - } else { - t.Log("RunResult success:", to) - } -} - -func Test_Object_AutoMapper_StructToMap(t *testing.T) { - m := NewMapper() - from := &FromStruct{Name: "From", Sex: true, AA: "AA"} - to := make(map[string]interface{}) - err := m.AutoMapper(from, &to) - if err != nil { - t.Error("RunResult error: mapper error", err) - } else { - if to["UserName"] == "From" { - t.Log("RunResult success:", to) - } else { - t.Error("RunResult failed: map[UserName]", to["UserName"]) - } - } -} - -func Test_Object_MapperMap(t *testing.T) { - m := NewMapper() - validateTime, _ := time.Parse("2006-01-02 15:04:05", "2017-01-01 10:00:00") - fromMap := make(map[string]interface{}) - fromMap["Name"] = "test" - fromMap["Sex"] = true - fromMap["Age"] = 10 - fromMap["Time"] = validateTime - fromMap["Time2"] = validateTime - toObj := &testStruct{} - err := m.MapperMap(fromMap, toObj) - if err != nil && toObj.Time != validateTime { - t.Error("RunResult error: mapper error", err) - } else { - t.Log("RunResult success:", toObj) - } -} - -func Test_Object_MapToSlice(t *testing.T) { - m := NewMapper() - var toSlice []*testStruct - fromMaps := make(map[string]interface{}) - for i := 0; i < 10; i++ { - fromMap := make(map[string]interface{}) - fromMap["Name"] = "s" + strconv.Itoa(i) - fromMap["Sex"] = true - fromMap["Age"] = i - fromMaps[strconv.Itoa(i)] = fromMap - } - err := m.MapToSlice(fromMaps, &toSlice) - if err != nil { - t.Error(err) - } else { - t.Log(toSlice, len(toSlice)) - } -} - -func Test_Object_MapperMapSlice(t *testing.T) { - m := NewMapper() - var toSlice []*testStruct - fromMaps := make(map[string]map[string]interface{}) - for i := 0; i < 10; i++ { - fromMap := make(map[string]interface{}) - fromMap["Name"] = "s" + strconv.Itoa(i) - fromMap["Sex"] = true - fromMap["Age"] = i - fromMaps[strconv.Itoa(i)] = fromMap - } - err := m.MapperMapSlice(fromMaps, &toSlice) - if err != nil { - t.Error(err) - } else { - t.Log(toSlice, len(toSlice)) - } -} - -func Test_Object_MapperStructMapSlice(t *testing.T) { - m := NewMapper() - var toSlice []testStruct - fromMaps := make(map[string]map[string]interface{}) - for i := 0; i < 10; i++ { - fromMap := make(map[string]interface{}) - fromMap["Name"] = "s" + strconv.Itoa(i) - fromMap["Sex"] = true - fromMap["Age"] = i - fromMaps[strconv.Itoa(i)] = fromMap - } - err := m.MapperMapSlice(fromMaps, &toSlice) - if err != nil { - t.Error(err) - } else { - t.Log(toSlice, len(toSlice)) - } - -} - -func Test_Object_IsTimeField(t *testing.T) { - m := NewMapper() - t1 := time.Now() - if m.GetDefaultTimeWrapper().IsType(reflect.ValueOf(t1)) { - t.Log("check time.Now ok") - } else { - t.Error("check time.Now error") - } - - var t2 JSONTime - t2 = JSONTime(time.Now()) - if m.GetDefaultTimeWrapper().IsType(reflect.ValueOf(t2)) { - t.Log("check mapper.Time ok") - } else { - t.Error("check mapper.Time error") - } -} - -func Test_Object_MapToJson_JsonToMap(t *testing.T) { - m := NewMapper() - fromMap := createMap() - data, err := m.MapToJson(fromMap) - if err != nil { - t.Error("MapToJson error", err) - } else { - var retMap map[string]interface{} - err = m.JsonToMap(data, &retMap) - if err != nil { - t.Error("MapToJson.JsonToMap error", err) - } - if len(retMap) != len(fromMap) { - t.Error("MapToJson failed, not match length") - } - t.Log("MapToJson success", fromMap, retMap) - } -} - -func BenchmarkObjectMapperMapSlice(b *testing.B) { - m := NewMapper() - var s []*testStruct - fromMaps := make(map[string]map[string]interface{}) - for i := 0; i < 10; i++ { - fromMap := make(map[string]interface{}) - fromMap["Name"] = "s" + strconv.Itoa(i) - fromMap["Sex"] = true - fromMap["Age"] = i - fromMaps[strconv.Itoa(i)] = fromMap - } - for i := 0; i < b.N; i++ { - m.MapperMapSlice(fromMaps, s) - } -} - -func BenchmarkObjectMapper(b *testing.B) { - m := NewMapper() - m.Register(&FromStruct{}) - m.Register(&ToStruct{}) - from := &FromStruct{Name: "From", Sex: true, AA: "AA"} - to := &ToStruct{} - - for i := 0; i < b.N; i++ { - m.Mapper(from, to) - } -} - -func BenchmarkObjectAutoMapper(b *testing.B) { - m := NewMapper() - m.Register(&FromStruct{}) - m.Register(&ToStruct{}) - from := &FromStruct{Name: "From", Sex: true, AA: "AA"} - to := &ToStruct{} - - for i := 0; i < b.N; i++ { - m.Mapper(from, to) - } -} - -func BenchmarkObjectAutoMapper_Map(b *testing.B) { - m := NewMapper() - from := &FromStruct{Name: "From", Sex: true, AA: "AA"} - to := make(map[string]interface{}) - - for i := 0; i < b.N; i++ { - m.Mapper(from, &to) - } -} - -func BenchmarkObjectMapperMap(b *testing.B) { - m := NewMapper() - m.Register(&testStruct{}) - fromMap := make(map[string]interface{}) - fromMap["Name"] = "test" - fromMap["Sex"] = true - fromMap["Age"] = 10 - fromMap["time"] = time.Now() - toObj := &testStruct{} - - for i := 0; i < b.N; i++ { - m.MapperMap(fromMap, toObj) - } -} - -func BenchmarkObjectSyncMap(b *testing.B) { - var sMap sync.Map - for i := 0; i < b.N; i++ { - sMap.Load("1") - } -} diff --git a/version.md b/version.md index 648b161..ce085ac 100644 --- a/version.md +++ b/version.md @@ -1,55 +1,5 @@ ## devfeel/mapper -#### Version 0.7.7 -* Feature: add Object-oriented interface for the mapper. -* comment: the old version implementation will be refactored in next release. -* About the new feature:: -``` - package main - -import ( - "fmt" - "time" - - "github.com/devfeel/mapper" -) -type ( - User struct { - Name string `json:"name" mapper:"name"` - Age int `json:"age" mapper:"age"` - } - - Student struct { - Name string `json:"name" mapper:"name"` - Age int `json:"age" mapper:"-"` - } -) - -func main() { - user := &User{Name: "test", Age: 10} - student := &Student{} - - // create mapper object - m := mapper.NewMapper() - - // enable the type checking - m.SetEnabledTypeChecking(true) - - student.Age = 1 - - // disable the json tag - m.SetEnabledJsonTag(false) - - // student::age should be 1 - m.Mapper(user, student) - - fmt.Println(student) -} -``` - -* Tips: Thanks to @aeramu for issue #12 -* 2021-10-19 12:00 in ShangHai -* #### Version 0.7.6 * Feature: add SetEnabledMapperTag to set enabled flag for 'Mapper' tag check * Feature: add SetEnabledJsonTag to set enabled flag for 'Json' tag check From 2e3f104df48711282ebc45bf1ba42e3b8219601e Mon Sep 17 00:00:00 2001 From: shyandsy Date: Fri, 15 Apr 2022 23:45:39 +0800 Subject: [PATCH 3/3] feature: object oriented api for the mapper --- README.md | 70 +++++- constant.go | 12 + convert.go | 5 +- example/object/go.mod | 7 + example/object/go.sum | 0 example/object/main.go | 39 +++ example/typewrapper/main.go | 18 +- go.mod | 5 + go.sum | 2 + mapper.go | 52 ++-- mapper_object.go | 341 ++++++++++++++++++++++++++ mapper_object_internal.go | 319 +++++++++++++++++++++++++ mapper_object_test.go | 465 ++++++++++++++++++++++++++++++++++++ version.md | 49 ++++ 14 files changed, 1355 insertions(+), 29 deletions(-) create mode 100644 constant.go create mode 100644 example/object/go.mod create mode 100644 example/object/go.sum create mode 100644 example/object/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 mapper_object.go create mode 100644 mapper_object_internal.go create mode 100644 mapper_object_test.go diff --git a/README.md b/README.md index f78561e..9eb4bda 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # devfeel/mapper + A simple and easy go tools for auto mapper struct to map, struct to struct, slice to slice, map to slice, map to json. ## 1. Install @@ -8,6 +9,8 @@ go get -u github.com/devfeel/mapper ``` ## 2. Getting Started + +Traditional Usage ```go package main @@ -66,8 +69,8 @@ func main() { fmt.Println("teacher", teacher) fmt.Println("userMap:", userMap) } - ``` + 执行main,输出: ``` student: &{test 10 testId 100} @@ -76,7 +79,59 @@ teacher &{test 10 testId } userMap: &{map 10 x1asd 100 2017-11-20 13:45:56.3972504 +0800 CST m=+0.006004001} ``` +Object Usage + +```go +package main + +import ( + "fmt" + "github.com/devfeel/mapper" +) + +type ( + User struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"age"` + } + + Student struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"-"` + } +) + +func main() { + user := &User{Name: "test", Age: 10} + student := &Student{} + + // create mapper object + m := mapper.NewMapper() + + // enable the type checking + m.SetEnabledTypeChecking(true) + + student.Age = 1 + + // disable the json tag + m.SetEnabledJsonTag(false) + + // student::age should be 1 + m.Mapper(user, student) + + fmt.Println(student) +} +``` + +执行main,输出: +``` +&{test 1} +``` + + + ## Features + * 支持不同结构体相同名称相同类型字段自动赋值,使用Mapper * 支持不同结构体Slice的自动赋值,使用MapperSlice * 支持字段为结构体时的自动赋值 @@ -88,4 +143,15 @@ userMap: &{map 10 x1asd 100 2017-11-20 13:45:56.3972504 +0800 CST m=+0.006004001 * 支持tag标签,tag关键字为 mapper * 兼容json-tag标签 * 当tag为"-"时,将忽略tag定义,使用struct field name -* 无需手动Register struct,内部自动识别 \ No newline at end of file +* 无需手动Register struct,内部自动识别 +* 支持开启关闭 + * SetEnabledTypeChecking(bool) // 类型检查 + * IsEnabledTypeChecking + * SetEnabledMapperTag // mapper tag + * IsEnabledMapperTag + * SetEnabledJsonTag // json tag + * IsEnabledJsonTag + * SetEnabledAutoTypeConvert // auto type convert + * IsEnabledAutoTypeConvert + * SetEnabledMapperStructField // mapper struct field + * IsEnabledMapperStructField \ No newline at end of file diff --git a/constant.go b/constant.go new file mode 100644 index 0000000..587fd79 --- /dev/null +++ b/constant.go @@ -0,0 +1,12 @@ +package mapper + +const ( + packageVersion = "0.7.6" + mapperTagKey = "mapper" + jsonTagKey = "json" + IgnoreTagValue = "-" + nameConnector = "_" + formatTime = "15:04:05" + formatDate = "2006-01-02" + formatDateTime = "2006-01-02 15:04:05" +) \ No newline at end of file diff --git a/convert.go b/convert.go index b7619da..c1e34ab 100644 --- a/convert.go +++ b/convert.go @@ -1,6 +1,7 @@ package mapper import ( + "encoding/hex" "fmt" "math/big" "reflect" @@ -22,12 +23,12 @@ func (f *Convert) Set(v string) { // Clear string func (f *Convert) Clear() { - *f = Convert(0x1E) + *f = Convert(hex.EncodeToString([]byte{0x1E})) } // Exist check string exist func (f Convert) Exist() bool { - return string(f) != string(0x1E) + return string(f) != hex.EncodeToString([]byte{0x1E}) } // Bool string to bool diff --git a/example/object/go.mod b/example/object/go.mod new file mode 100644 index 0000000..6734ac5 --- /dev/null +++ b/example/object/go.mod @@ -0,0 +1,7 @@ +module object + +go 1.16 + +replace github.com/devfeel/mapper v0.7.6 => ./../../../mapper + +require github.com/devfeel/mapper v0.7.6 diff --git a/example/object/go.sum b/example/object/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/example/object/main.go b/example/object/main.go new file mode 100644 index 0000000..583d788 --- /dev/null +++ b/example/object/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "github.com/devfeel/mapper" +) + +type ( + User struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"age"` + } + + Student struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"-"` + } +) + +func main() { + user := &User{Name: "test", Age: 10} + student := &Student{} + + // create mapper object + m := mapper.NewMapper() + + // enable the type checking + m.SetEnabledTypeChecking(true) + + student.Age = 1 + + // disable the json tag + m.SetEnabledJsonTag(false) + + // student::age should be 1 + m.Mapper(user, student) + + fmt.Println(student) +} diff --git a/example/typewrapper/main.go b/example/typewrapper/main.go index 89148a6..696e27d 100644 --- a/example/typewrapper/main.go +++ b/example/typewrapper/main.go @@ -1,8 +1,6 @@ package main import ( - "fmt" - "github.com/devfeel/mapper" "reflect" "time" ) @@ -28,7 +26,7 @@ type decimal struct { } type DecimalWrapper struct { - mapper.BaseTypeWrapper + // mapper.BaseTypeWrapper } func (w *DecimalWrapper) IsType(value reflect.Value) bool { @@ -39,12 +37,14 @@ func (w *DecimalWrapper) IsType(value reflect.Value) bool { } func main() { - mapper.UseWrapper(&DecimalWrapper{}) - user := &User{Name: "test", Age: 10, Score: decimal{value: 1}, Time: time.Now()} - stu := &Student{} + /* + mapper.UseWrapper(&DecimalWrapper{}) + user := &User{Name: "test", Age: 10, Score: decimal{value: 1}, Time: time.Now()} + stu := &Student{} - mapper.AutoMapper(user, stu) + mapper.AutoMapper(user, stu) - fmt.Println(user) - fmt.Println(stu) + fmt.Println(user) + fmt.Println(stu) + */ } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6ff406d --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module mapper + +go 1.16 + +require github.com/devfeel/mapper v0.7.6 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1c15dea --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/devfeel/mapper v0.7.6 h1:FItOzW4YpEp1x+U1izauW5+sbVIiymOwF7AiWxIdUVk= +github.com/devfeel/mapper v0.7.6/go.mod h1:foz4u16jrssGoDfnWYQGFcthjlU6uBV5UV8uYJfKneA= diff --git a/mapper.go b/mapper.go index ee19c01..0bb7d5a 100644 --- a/mapper.go +++ b/mapper.go @@ -23,16 +23,36 @@ var ( typeWrappers []TypeWrapper ) -const ( - packageVersion = "0.7.6" - mapperTagKey = "mapper" - jsonTagKey = "json" - IgnoreTagValue = "-" - nameConnector = "_" - formatTime = "15:04:05" - formatDate = "2006-01-02" - formatDateTime = "2006-01-02 15:04:05" -) +type IMapper interface { + Mapper(fromObj, toObj interface{}) error + AutoMapper(fromObj, toObj interface{}) error + MapperMap(fromMap map[string]interface{}, toObj interface{}) error + Register(obj interface{}) error + GetTypeName(obj interface{}) string + GetFieldName(objElem reflect.Value, index int) string + GetDefaultTimeWrapper() *TimeWrapper + CheckExistsField(elem reflect.Value, fieldName string) (realFieldName string, exists bool) + MapToSlice(fromMap map[string]interface{}, toSlice interface{}) error + MapperMapSlice(fromMaps map[string]map[string]interface{}, toSlice interface{}) error + MapperSlice(fromSlice, toSlice interface{}) error + MapToJson(fromMap map[string]interface{}) ([]byte, error) + JsonToMap(body []byte, toMap *map[string]interface{}) error + + SetEnabledTypeChecking(isEnabled bool) + IsEnabledTypeChecking() bool + + SetEnabledMapperTag(isEnabled bool) + IsEnabledMapperTag() bool + + SetEnabledJsonTag(isEnabled bool) + IsEnabledJsonTag() bool + + SetEnabledAutoTypeConvert(isEnabled bool) + IsEnabledAutoTypeConvert() bool + + SetEnabledMapperStructField(isEnabled bool) + IsEnabledMapperStructField() bool +} func init() { ZeroValue = reflect.Value{} @@ -136,7 +156,7 @@ func registerValue(objValue reflect.Value) error { } } - //store register flag + // store register flag registerMap.Store(typeName, nil) return nil } @@ -194,14 +214,14 @@ func MapperMap(fromMap map[string]interface{}, toObj interface{}) error { if toElem == ZeroValue { return errors.New("to obj is not legal value") } - //check register flag - //if not register, register it + // check register flag + // if not register, register it if !checkIsRegister(toElem) { Register(toObj) } for k, v := range fromMap { fieldName := k - //check field is exists + // check field is exists realFieldName, exists := CheckExistsField(toElem, fieldName) if !exists { continue @@ -274,7 +294,7 @@ func MapperMapSlice(fromMaps map[string]map[string]interface{}, toSlice interfac toElemType := reflect.TypeOf(toSlice).Elem().Elem() realType := toElemType.Kind() direct := reflect.Indirect(toValue) - //3 elem parse: 1.[]*type 2.*type 3.type + // 3 elem parse: 1.[]*type 2.*type 3.type if realType == reflect.Ptr { toElemType = toElemType.Elem() } @@ -307,7 +327,7 @@ func MapperSlice(fromSlice, toSlice interface{}) error { elemType := reflect.TypeOf(toSlice).Elem().Elem() realType := elemType.Kind() direct := reflect.Indirect(toValue) - //3 elem parse: 1.[]*type 2.*type 3.type + // 3 elem parse: 1.[]*type 2.*type 3.type if realType == reflect.Ptr { elemType = elemType.Elem() } diff --git a/mapper_object.go b/mapper_object.go new file mode 100644 index 0000000..54083ab --- /dev/null +++ b/mapper_object.go @@ -0,0 +1,341 @@ +package mapper + +import ( + "encoding/json" + "errors" + "reflect" + "sync" + "time" +) + +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 +} + +func NewMapper() IMapper { + 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, + } + dm.useWrapper(dm.DefaultTimeWrapper) + return &dm +} + +// Mapper map and set value from struct fromObj to toObj +// not support auto register struct +func (dm *mapperObject) Mapper(fromObj, toObj interface{}) error { + fromElem := reflect.ValueOf(fromObj).Elem() + toElem := reflect.ValueOf(toObj).Elem() + if fromElem == dm.ZeroValue { + return errors.New("from obj is not legal value") + } + if toElem == dm.ZeroValue { + return errors.New("to obj is not legal value") + } + return dm.elemMapper(fromElem, toElem) +} + +// AutoMapper mapper and set value from struct fromObj to toObj +// support auto register struct +func (dm *mapperObject) AutoMapper(fromObj, toObj interface{}) error { + return dm.Mapper(fromObj, toObj) +} + +// MapperMap mapper and set value from map to object +// support auto register struct +// now support field type: +// 1.reflect.Bool +// 2.reflect.String +// 3.reflect.Int8\16\32\64 +// 4.reflect.Uint8\16\32\64 +// 5.reflect.Float32\64 +// 6.time.Time +func (dm *mapperObject) MapperMap(fromMap map[string]interface{}, toObj interface{}) error { + toElemType := reflect.ValueOf(toObj) + toElem := toElemType + if toElemType.Kind() == reflect.Ptr { + toElem = toElemType.Elem() + } + + if toElem == dm.ZeroValue { + return errors.New("to obj is not legal value") + } + // check register flag + // if not register, register it + if !dm.checkIsRegister(toElem) { + if err := dm.Register(toObj); err != nil { + return err + } + } + for k, v := range fromMap { + fieldName := k + // check field is exists + realFieldName, exists := dm.CheckExistsField(toElem, fieldName) + if !exists { + continue + } + fieldInfo, exists := toElem.Type().FieldByName(realFieldName) + if !exists { + continue + } + + fieldKind := fieldInfo.Type.Kind() + fieldValue := toElem.FieldByName(realFieldName) + + if err := dm.setFieldValue(fieldValue, fieldKind, v); err != nil { + return err + } + } + return nil +} + +// SetEnabledTypeChecking set enabled flag for TypeChecking +// 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 +} + +func (dm *mapperObject) IsEnabledTypeChecking() bool { + return dm.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 +} + +func (dm *mapperObject) IsEnabledMapperTag() bool { + return dm.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 +} + +func (dm *mapperObject) IsEnabledJsonTag() bool { + return dm.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 +} + +func (dm *mapperObject) IsEnabledAutoTypeConvert() bool { + return dm.enabledAutoTypeConvert +} + +// SetEnabledMapperStructField set enabled flag for MapperStructField +// if set true, the reflect.Struct field will auto mapper +// must follow premises: +// 1. fromField and toField type must be reflect.Struct and not time.Time +// 2. fromField and toField must be not same type +// default is enabled +func (dm *mapperObject) SetEnabledMapperStructField(isEnabled bool) { + dm.enabledMapperStructField = isEnabled +} + +func (dm *mapperObject) IsEnabledMapperStructField() bool { + return dm.enabledMapperStructField +} + +// GetTypeName get type name +func (dm *mapperObject) GetTypeName(obj interface{}) string { + object := reflect.ValueOf(obj) + return object.String() +} + +// GetFieldName get fieldName with ElemValue and index +// if config tag string, return tag value +func (dm *mapperObject) GetFieldName(objElem reflect.Value, index int) string { + fieldName := "" + field := objElem.Type().Field(index) + tag := dm.getStructTag(field) + if tag != "" { + fieldName = tag + } else { + fieldName = field.Name + } + return fieldName +} + +func (dm *mapperObject) GetDefaultTimeWrapper() *TimeWrapper { + return dm.DefaultTimeWrapper +} + +// Register register struct to init Map +func (dm *mapperObject) Register(obj interface{}) error { + objValue := reflect.ValueOf(obj) + if objValue == dm.ZeroValue { + return errors.New("obj value does not exist") + } + return dm.registerValue(objValue) +} + +// MapToSlice mapper from map[string]interface{} to a slice of any type's ptr +// toSlice must be a slice of any type. +func (dm *mapperObject) MapToSlice(fromMap map[string]interface{}, toSlice interface{}) error { + var err error + toValue := reflect.ValueOf(toSlice) + if toValue.Kind() != reflect.Ptr { + return errors.New("toSlice must be a pointer to a slice") + } + if toValue.IsNil() { + return errors.New("toSlice must not be a nil pointer") + } + + toElemType := reflect.TypeOf(toSlice).Elem().Elem() + realType := toElemType.Kind() + direct := reflect.Indirect(toValue) + if realType == reflect.Ptr { + toElemType = toElemType.Elem() + } + for _, v := range fromMap { + if reflect.TypeOf(v).Kind().String() == "map" { + elem := reflect.New(toElemType) + err = dm.MapperMap(v.(map[string]interface{}), elem.Interface()) + if err == nil { + if realType == reflect.Ptr { + direct.Set(reflect.Append(direct, elem)) + } else { + direct.Set(reflect.Append(direct, elem).Elem()) + } + } + } else { + if realType == reflect.Ptr { + direct.Set(reflect.Append(direct, reflect.ValueOf(v))) + } else { + direct.Set(reflect.Append(direct, reflect.ValueOf(v).Elem())) + } + } + + } + return err +} + +// MapperMapSlice mapper from map[string]map[string]interface{} to a slice of any type's ptr +// toSlice must be a slice of any type. +// Deprecated: will remove on v1.0, please use MapToSlice instead +func (dm *mapperObject) MapperMapSlice(fromMaps map[string]map[string]interface{}, toSlice interface{}) error { + var err error + toValue := reflect.ValueOf(toSlice) + if toValue.Kind() != reflect.Ptr { + return errors.New("toSlice must be a pointer to a slice") + } + if toValue.IsNil() { + return errors.New("toSlice must not be a nil pointer") + } + + toElemType := reflect.TypeOf(toSlice).Elem().Elem() + realType := toElemType.Kind() + direct := reflect.Indirect(toValue) + // 3 elem parse: 1.[]*type 2.*type 3.type + if realType == reflect.Ptr { + toElemType = toElemType.Elem() + } + for _, v := range fromMaps { + elem := reflect.New(toElemType) + err = dm.MapperMap(v, elem.Interface()) + if err == nil { + if realType == reflect.Ptr { + direct.Set(reflect.Append(direct, elem)) + } else { + direct.Set(reflect.Append(direct, elem.Elem())) + } + } + } + return err +} + +// MapperSlice mapper from slice of struct to a slice of any type +// fromSlice and toSlice must be a slice of any type. +func (dm *mapperObject) MapperSlice(fromSlice, toSlice interface{}) error { + var err error + toValue := reflect.ValueOf(toSlice) + if toValue.Kind() != reflect.Ptr { + return errors.New("toSlice must be a pointer to a slice") + } + if toValue.IsNil() { + return errors.New("toSlice must not be a nil pointer") + } + + elemType := reflect.TypeOf(toSlice).Elem().Elem() + realType := elemType.Kind() + direct := reflect.Indirect(toValue) + // 3 elem parse: 1.[]*type 2.*type 3.type + if realType == reflect.Ptr { + elemType = elemType.Elem() + } + + fromElems := dm.convertToSlice(fromSlice) + for _, v := range fromElems { + elem := reflect.New(elemType).Elem() + if realType == reflect.Ptr { + elem = reflect.New(elemType) + } + if realType == reflect.Ptr { + err = dm.elemMapper(reflect.ValueOf(v).Elem(), elem.Elem()) + } else { + err = dm.elemMapper(reflect.ValueOf(v), elem) + } + if err == nil { + direct.Set(reflect.Append(direct, elem)) + } + } + return err +} + +// MapToJson mapper from map[string]interface{} to json []byte +func (dm *mapperObject) MapToJson(fromMap map[string]interface{}) ([]byte, error) { + jsonStr, err := json.Marshal(fromMap) + if err != nil { + return nil, err + } + return jsonStr, nil +} + +// JsonToMap mapper from json []byte to map[string]interface{} +func (dm *mapperObject) JsonToMap(body []byte, toMap *map[string]interface{}) error { + err := json.Unmarshal(body, toMap) + return err +} + +// CheckExistsField check field is exists by name +func (dm *mapperObject) CheckExistsField(elem reflect.Value, fieldName string) (realFieldName string, exists bool) { + typeName := elem.Type().String() + fileKey := typeName + nameConnector + fieldName + realName, isOk := dm.fieldNameMap.Load(fileKey) + + if !isOk { + return "", isOk + } else { + return realName.(string), isOk + } +} diff --git a/mapper_object_internal.go b/mapper_object_internal.go new file mode 100644 index 0000000..f9bc5fe --- /dev/null +++ b/mapper_object_internal.go @@ -0,0 +1,319 @@ +package mapper + +import ( + "errors" + "fmt" + "reflect" + "strings" + "time" +) + +// registerValue register Value to init Map +func (dm *mapperObject) registerValue(objValue reflect.Value) error { + regValue := objValue + if objValue == dm.ZeroValue { + return errors.New("obj value does not exist") + } + + if regValue.Type().Kind() == reflect.Ptr { + regValue = regValue.Elem() + } + + typeName := regValue.Type().String() + if regValue.Type().Kind() == reflect.Struct { + for i := 0; i < regValue.NumField(); i++ { + mapFieldName := typeName + nameConnector + dm.getFieldName(regValue, i) + realFieldName := regValue.Type().Field(i).Name + dm.fieldNameMap.Store(mapFieldName, realFieldName) + } + } + + // store register flag + dm.registerMap.Store(typeName, nil) + return nil +} + +// GetFieldName get fieldName with ElemValue and index +// if config tag string, return tag value +func (dm *mapperObject) getFieldName(objElem reflect.Value, index int) string { + fieldName := "" + field := objElem.Type().Field(index) + tag := dm.getStructTag(field) + if tag != "" { + fieldName = tag + } else { + fieldName = field.Name + } + return fieldName +} + +// UseWrapper register a type wrapper +func (dm *mapperObject) useWrapper(w TypeWrapper) { + if len(dm.typeWrappers) > 0 { + dm.typeWrappers[len(dm.typeWrappers)-1].SetNext(w) + } + dm.typeWrappers = append(dm.typeWrappers, w) +} + +func (dm *mapperObject) elemMapper(fromElem, toElem reflect.Value) error { + // check register flag + // if not register, register it + if !dm.checkIsRegister(fromElem) { + if err := dm.registerValue(fromElem); err != nil { + return err + } + } + if !dm.checkIsRegister(toElem) { + if err := dm.registerValue(toElem); err != nil { + return err + } + } + if toElem.Type().Kind() == reflect.Map { + dm.elemToMap(fromElem, toElem) + } else { + dm.elemToStruct(fromElem, toElem) + } + + return nil +} + +func (dm *mapperObject) elemToStruct(fromElem, toElem reflect.Value) { + for i := 0; i < fromElem.NumField(); i++ { + fromFieldInfo := fromElem.Field(i) + fieldName := dm.getFieldName(fromElem, i) + // check field is exists + realFieldName, exists := dm.CheckExistsField(toElem, fieldName) + if !exists { + continue + } + + toFieldInfo := toElem.FieldByName(realFieldName) + // check field is same type + if dm.enabledTypeChecking { + if fromFieldInfo.Kind() != toFieldInfo.Kind() { + continue + } + } + + if dm.enabledMapperStructField && + toFieldInfo.Kind() == reflect.Struct && fromFieldInfo.Kind() == reflect.Struct && + toFieldInfo.Type() != fromFieldInfo.Type() && + !dm.checkIsTypeWrapper(toFieldInfo) && !dm.checkIsTypeWrapper(fromFieldInfo) { + x := reflect.New(toFieldInfo.Type()).Elem() + err := dm.elemMapper(fromFieldInfo, x) + if err != nil { + fmt.Println("auto mapper field", fromFieldInfo, "=>", toFieldInfo, "error", err.Error()) + } else { + toFieldInfo.Set(x) + } + } else { + isSet := false + if dm.enabledAutoTypeConvert { + if dm.DefaultTimeWrapper.IsType(fromFieldInfo) && toFieldInfo.Kind() == reflect.Int64 { + fromTime := fromFieldInfo.Interface().(time.Time) + toFieldInfo.Set(reflect.ValueOf(TimeToUnix(fromTime))) + isSet = true + } else if dm.DefaultTimeWrapper.IsType(toFieldInfo) && fromFieldInfo.Kind() == reflect.Int64 { + fromTime := fromFieldInfo.Interface().(int64) + toFieldInfo.Set(reflect.ValueOf(UnixToTime(fromTime))) + isSet = true + } + } + if !isSet { + toFieldInfo.Set(fromFieldInfo) + } + } + + } +} + +func (dm *mapperObject) elemToMap(fromElem, toElem reflect.Value) { + for i := 0; i < fromElem.NumField(); i++ { + fromFieldInfo := fromElem.Field(i) + fieldName := dm.getFieldName(fromElem, i) + toElem.SetMapIndex(reflect.ValueOf(fieldName), fromFieldInfo) + } +} + +func (dm *mapperObject) setFieldValue(fieldValue reflect.Value, fieldKind reflect.Kind, value interface{}) error { + switch fieldKind { + case reflect.Bool: + if value == nil { + fieldValue.SetBool(false) + } else if v, ok := value.(bool); ok { + fieldValue.SetBool(v) + } else { + v, _ := Convert(ToString(value)).Bool() + fieldValue.SetBool(v) + } + + case reflect.String: + if value == nil { + fieldValue.SetString("") + } else { + fieldValue.SetString(ToString(value)) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if value == nil { + fieldValue.SetInt(0) + } else { + val := reflect.ValueOf(value) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fieldValue.SetInt(val.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + fieldValue.SetInt(int64(val.Uint())) + default: + v, _ := Convert(ToString(value)).Int64() + fieldValue.SetInt(v) + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if value == nil { + fieldValue.SetUint(0) + } else { + val := reflect.ValueOf(value) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fieldValue.SetUint(uint64(val.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + fieldValue.SetUint(val.Uint()) + default: + v, _ := Convert(ToString(value)).Uint64() + fieldValue.SetUint(v) + } + } + case reflect.Float64, reflect.Float32: + if value == nil { + fieldValue.SetFloat(0) + } else { + val := reflect.ValueOf(value) + switch val.Kind() { + case reflect.Float64: + fieldValue.SetFloat(val.Float()) + default: + v, _ := Convert(ToString(value)).Float64() + fieldValue.SetFloat(v) + } + } + case reflect.Struct: + if value == nil { + fieldValue.Set(reflect.Zero(fieldValue.Type())) + } else if dm.DefaultTimeWrapper.IsType(fieldValue) { + var timeString string + if fieldValue.Type() == dm.timeType { + timeString = "" + fieldValue.Set(reflect.ValueOf(value)) + } + if fieldValue.Type() == dm.jsonTimeType { + timeString = "" + fieldValue.Set(reflect.ValueOf(JSONTime(value.(time.Time)))) + } + switch d := value.(type) { + case []byte: + timeString = string(d) + case string: + timeString = d + case int64: + if dm.enabledAutoTypeConvert { + // try to transform Unix time to local Time + t, err := UnixToTimeLocation(value.(int64), time.UTC.String()) + if err != nil { + return err + } + fieldValue.Set(reflect.ValueOf(t)) + } + } + if timeString != "" { + if len(timeString) >= 19 { + // 满足yyyy-MM-dd HH:mm:ss格式 + timeString = timeString[:19] + t, err := time.ParseInLocation(formatDateTime, timeString, time.UTC) + if err == nil { + t = t.In(time.UTC) + fieldValue.Set(reflect.ValueOf(t)) + } + } else if len(timeString) >= 10 { + // 满足yyyy-MM-dd格式 + timeString = timeString[:10] + t, err := time.ParseInLocation(formatDate, timeString, time.UTC) + if err == nil { + fieldValue.Set(reflect.ValueOf(t)) + } + } + } + } + default: + if reflect.ValueOf(value).Type() == fieldValue.Type() { + fieldValue.Set(reflect.ValueOf(value)) + } + } + + return nil +} + +func (dm *mapperObject) getStructTag(field reflect.StructField) string { + tagValue := "" + // 1.check mapperTagKey + if dm.enabledMapperTag { + tagValue = field.Tag.Get(mapperTagKey) + if dm.checkTagValidity(tagValue) { + return tagValue + } + } + + // 2.check jsonTagKey + if dm.enabledJsonTag { + tagValue = field.Tag.Get(jsonTagKey) + if dm.checkTagValidity(tagValue) { + // support more tag property, as json tag omitempty 2018-07-13 + return strings.Split(tagValue, ",")[0] + } + } + + return "" +} + +func (dm *mapperObject) checkTagValidity(tagValue string) bool { + if tagValue != "" && tagValue != IgnoreTagValue { + return true + } + return false +} + +func (dm *mapperObject) checkIsRegister(objElem reflect.Value) bool { + typeName := objElem.Type().String() + _, isOk := dm.registerMap.Load(typeName) + return isOk +} + +// convert slice interface{} to []interface{} +func (dm *mapperObject) convertToSlice(arr interface{}) []interface{} { + v := reflect.ValueOf(arr) + if v.Kind() == reflect.Ptr { + if v.Elem().Kind() != reflect.Slice { + panic("fromSlice arr is not a pointer to a slice") + } + v = v.Elem() + } else { + if v.Kind() != reflect.Slice { + panic("fromSlice arr is not a slice") + } + } + l := v.Len() + ret := make([]interface{}, l) + for i := 0; i < l; i++ { + ret[i] = v.Index(i).Interface() + } + return ret +} + +// CheckIsTypeWrapper check value is in type wrappers +func (dm *mapperObject) checkIsTypeWrapper(value reflect.Value) bool { + for _, w := range dm.typeWrappers { + if w.IsType(value) { + return true + } + } + return false +} diff --git a/mapper_object_test.go b/mapper_object_test.go new file mode 100644 index 0000000..fac2bc5 --- /dev/null +++ b/mapper_object_test.go @@ -0,0 +1,465 @@ +package mapper + +import ( + "reflect" + "strconv" + "sync" + "testing" + "time" +) + +func Test_Object_SetEnabledTypeChecking(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + if m.IsEnabledTypeChecking() != true { + t.Error("SetEnabledTypeChecking error: set true but query is not true") + } else { + t.Log("SetEnabledTypeChecking success") + } +} + +func Test_ObjectGetTypeName(t *testing.T) { + m := NewMapper() + name := m.GetTypeName(&testStruct{}) + if name == "" { + t.Error("RunResult error: name is empty") + } else { + t.Log("RunResult success:", name) + } +} + +func BenchmarkObjectGetTypeName(b *testing.B) { + m := NewMapper() + for i := 0; i < b.N; i++ { + m.GetTypeName(&testStruct{}) + } +} + +func Test_Object_GetFieldNameFromMapperTag(t *testing.T) { + m := NewMapper() + v := TagStruct{} + fieldName := m.GetFieldName(reflect.ValueOf(v), 0) + if fieldName == "UserName" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } +} + +func Test_Object_GetFieldNameFromJsonTag(t *testing.T) { + m := NewMapper() + v := TagStruct{} + fieldName := m.GetFieldName(reflect.ValueOf(v), 1) + if fieldName == "UserSex" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } +} + +func Test_Object_SetEnableMapperTag(t *testing.T) { + m := NewMapper() + v := TagStruct{} + m.SetEnabledMapperTag(false) + fieldName := m.GetFieldName(reflect.ValueOf(v), 0) + if fieldName == "Name" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } + m.SetEnabledMapperTag(true) + fieldName = m.GetFieldName(reflect.ValueOf(v), 0) + if fieldName == "UserName" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } +} + +func Test_Object_SetEnableJsonTag(t *testing.T) { + m := NewMapper() + v := TagStruct{} + m.SetEnabledJsonTag(false) + fieldName := m.GetFieldName(reflect.ValueOf(v), 1) + if fieldName == "Sex" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } + m.SetEnabledJsonTag(true) + fieldName = m.GetFieldName(reflect.ValueOf(v), 1) + if fieldName == "UserSex" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } +} + +func Test_Object_GetFieldNameWithElem(t *testing.T) { + m := NewMapper() + fieldName := m.GetFieldName(testValue.Elem(), 0) + if fieldName == "Name" { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not match", fieldName) + } +} + +func BenchmarkObjectGetFieldNameWithElem(b *testing.B) { + m := NewMapper() + for i := 0; i < b.N; i++ { + m.GetFieldName(testValue.Elem(), 0) + } +} + +func Test_Object_CheckExistsField(t *testing.T) { + m := NewMapper() + m.Register(&testStruct{}) + fieldName := "Name" + _, isOk := m.CheckExistsField(testValue.Elem(), fieldName) + if isOk { + t.Log("RunResult success:", fieldName) + } else { + t.Error("RunResult error: fieldName not exists", fieldName) + } +} + +func BenchmarkObjectCheckExistsField(b *testing.B) { + m := NewMapper() + m.Register(&testStruct{}) + elem := testValue.Elem() + fieldName := "Name" + for i := 0; i < b.N; i++ { + m.CheckExistsField(elem, fieldName) + } +} + +func Test_Object_Mapper(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := &ToStruct{} + err := m.Mapper(from, to) + if err != nil { + t.Error("RunResult error: mapper error", err) + } else { + t.Log("RunResult success:", to) + } +} + +func Test_Object_MapperSlice(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + var fromSlice []*FromStruct + var toSlice []*ToStruct + for i := 0; i < 10; i++ { + fromSlice = append(fromSlice, &FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) + } + err := m.MapperSlice(fromSlice, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + for i := 0; i < len(fromSlice); i++ { + if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || + !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || + !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { + t.Fail() + } + } + } +} + +func Test_Object_MapperSlice2(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + var fromSlice []*FromStruct + var toSlice []*ToStruct + for i := 0; i < 10; i++ { + fromSlice = append(fromSlice, &FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) + } + err := m.MapperSlice(&fromSlice, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + for i := 0; i < len(fromSlice); i++ { + if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || + !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || + !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { + t.Fail() + } + } + } +} + +func Test_Object_MapperStructSlice(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + var fromSlice []FromStruct + var toSlice []ToStruct + for i := 0; i < 10; i++ { + fromSlice = append(fromSlice, FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) + } + err := m.MapperSlice(fromSlice, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + for i := 0; i < len(fromSlice); i++ { + if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || + !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || + !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { + t.Fail() + } + } + } +} + +func Test_Object_MapperStructSlice2(t *testing.T) { + m := NewMapper() + m.SetEnabledTypeChecking(true) + var fromSlice []FromStruct + var toSlice []ToStruct + for i := 0; i < 10; i++ { + fromSlice = append(fromSlice, FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) + } + err := m.MapperSlice(&fromSlice, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + for i := 0; i < len(fromSlice); i++ { + if !reflect.DeepEqual(fromSlice[i].Name, toSlice[i].Name) || + !reflect.DeepEqual(fromSlice[i].Sex, toSlice[i].Sex) || + !reflect.DeepEqual(fromSlice[i].AA, toSlice[i].BB) { + t.Fail() + } + } + } +} + +func BenchmarkObjectMapperSlice(b *testing.B) { + m := NewMapper() + var fromSlice []*FromStruct + var toSlice []*ToStruct + for i := 0; i < 10; i++ { + fromSlice = append(fromSlice, &FromStruct{Name: "From" + strconv.Itoa(i), Sex: true, AA: "AA" + strconv.Itoa(i)}) + } + for i := 0; i < b.N; i++ { + m.MapperSlice(fromSlice, &toSlice) + } +} + +func Test_Object_AutoMapper(t *testing.T) { + m := NewMapper() + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := &ToStruct{} + err := m.AutoMapper(from, to) + if err != nil { + t.Error("RunResult error: mapper error", err) + } else { + t.Log("RunResult success:", to) + } +} + +func Test_Object_AutoMapper_StructToMap(t *testing.T) { + m := NewMapper() + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := make(map[string]interface{}) + err := m.AutoMapper(from, &to) + if err != nil { + t.Error("RunResult error: mapper error", err) + } else { + if to["UserName"] == "From" { + t.Log("RunResult success:", to) + } else { + t.Error("RunResult failed: map[UserName]", to["UserName"]) + } + } +} + +func Test_Object_MapperMap(t *testing.T) { + m := NewMapper() + validateTime, _ := time.Parse("2006-01-02 15:04:05", "2017-01-01 10:00:00") + fromMap := make(map[string]interface{}) + fromMap["Name"] = "test" + fromMap["Sex"] = true + fromMap["Age"] = 10 + fromMap["Time"] = validateTime + fromMap["Time2"] = validateTime + toObj := &testStruct{} + err := m.MapperMap(fromMap, toObj) + if err != nil && toObj.Time != validateTime { + t.Error("RunResult error: mapper error", err) + } else { + t.Log("RunResult success:", toObj) + } +} + +func Test_Object_MapToSlice(t *testing.T) { + m := NewMapper() + var toSlice []*testStruct + fromMaps := make(map[string]interface{}) + for i := 0; i < 10; i++ { + fromMap := make(map[string]interface{}) + fromMap["Name"] = "s" + strconv.Itoa(i) + fromMap["Sex"] = true + fromMap["Age"] = i + fromMaps[strconv.Itoa(i)] = fromMap + } + err := m.MapToSlice(fromMaps, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + } +} + +func Test_Object_MapperMapSlice(t *testing.T) { + m := NewMapper() + var toSlice []*testStruct + fromMaps := make(map[string]map[string]interface{}) + for i := 0; i < 10; i++ { + fromMap := make(map[string]interface{}) + fromMap["Name"] = "s" + strconv.Itoa(i) + fromMap["Sex"] = true + fromMap["Age"] = i + fromMaps[strconv.Itoa(i)] = fromMap + } + err := m.MapperMapSlice(fromMaps, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + } +} + +func Test_Object_MapperStructMapSlice(t *testing.T) { + m := NewMapper() + var toSlice []testStruct + fromMaps := make(map[string]map[string]interface{}) + for i := 0; i < 10; i++ { + fromMap := make(map[string]interface{}) + fromMap["Name"] = "s" + strconv.Itoa(i) + fromMap["Sex"] = true + fromMap["Age"] = i + fromMaps[strconv.Itoa(i)] = fromMap + } + err := m.MapperMapSlice(fromMaps, &toSlice) + if err != nil { + t.Error(err) + } else { + t.Log(toSlice, len(toSlice)) + } + +} + +func Test_Object_IsTimeField(t *testing.T) { + m := NewMapper() + t1 := time.Now() + if m.GetDefaultTimeWrapper().IsType(reflect.ValueOf(t1)) { + t.Log("check time.Now ok") + } else { + t.Error("check time.Now error") + } + + var t2 JSONTime + t2 = JSONTime(time.Now()) + if m.GetDefaultTimeWrapper().IsType(reflect.ValueOf(t2)) { + t.Log("check mapper.Time ok") + } else { + t.Error("check mapper.Time error") + } +} + +func Test_Object_MapToJson_JsonToMap(t *testing.T) { + m := NewMapper() + fromMap := createMap() + data, err := m.MapToJson(fromMap) + if err != nil { + t.Error("MapToJson error", err) + } else { + var retMap map[string]interface{} + err = m.JsonToMap(data, &retMap) + if err != nil { + t.Error("MapToJson.JsonToMap error", err) + } + if len(retMap) != len(fromMap) { + t.Error("MapToJson failed, not match length") + } + t.Log("MapToJson success", fromMap, retMap) + } +} + +func BenchmarkObjectMapperMapSlice(b *testing.B) { + m := NewMapper() + var s []*testStruct + fromMaps := make(map[string]map[string]interface{}) + for i := 0; i < 10; i++ { + fromMap := make(map[string]interface{}) + fromMap["Name"] = "s" + strconv.Itoa(i) + fromMap["Sex"] = true + fromMap["Age"] = i + fromMaps[strconv.Itoa(i)] = fromMap + } + for i := 0; i < b.N; i++ { + m.MapperMapSlice(fromMaps, s) + } +} + +func BenchmarkObjectMapper(b *testing.B) { + m := NewMapper() + m.Register(&FromStruct{}) + m.Register(&ToStruct{}) + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := &ToStruct{} + + for i := 0; i < b.N; i++ { + m.Mapper(from, to) + } +} + +func BenchmarkObjectAutoMapper(b *testing.B) { + m := NewMapper() + m.Register(&FromStruct{}) + m.Register(&ToStruct{}) + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := &ToStruct{} + + for i := 0; i < b.N; i++ { + m.Mapper(from, to) + } +} + +func BenchmarkObjectAutoMapper_Map(b *testing.B) { + m := NewMapper() + from := &FromStruct{Name: "From", Sex: true, AA: "AA"} + to := make(map[string]interface{}) + + for i := 0; i < b.N; i++ { + m.Mapper(from, &to) + } +} + +func BenchmarkObjectMapperMap(b *testing.B) { + m := NewMapper() + m.Register(&testStruct{}) + fromMap := make(map[string]interface{}) + fromMap["Name"] = "test" + fromMap["Sex"] = true + fromMap["Age"] = 10 + fromMap["time"] = time.Now() + toObj := &testStruct{} + + for i := 0; i < b.N; i++ { + m.MapperMap(fromMap, toObj) + } +} + +func BenchmarkObjectSyncMap(b *testing.B) { + var sMap sync.Map + for i := 0; i < b.N; i++ { + sMap.Load("1") + } +} diff --git a/version.md b/version.md index ce085ac..9fab417 100644 --- a/version.md +++ b/version.md @@ -1,5 +1,54 @@ ## devfeel/mapper +#### Version 0.7.7 +* Feature: add Object-oriented interface for the mapper. +* comment: the old version implementation will be refactored in next release. +* Tips: Thanks to @shyandsy +* About the new feature:: +``` + package main + +import ( + "fmt" + "time" + + "github.com/devfeel/mapper" +) +type ( + User struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"age"` + } + + Student struct { + Name string `json:"name" mapper:"name"` + Age int `json:"age" mapper:"-"` + } +) + +func main() { + user := &User{Name: "test", Age: 10} + student := &Student{} + + // create mapper object + m := mapper.NewMapper() + + // enable the type checking + m.SetEnabledTypeChecking(true) + + student.Age = 1 + + // disable the json tag + m.SetEnabledJsonTag(false) + + // student::age should be 1 + m.Mapper(user, student) + + fmt.Println(student) +} +``` +* 2022-04-15 23:00 in ShangHai + #### Version 0.7.6 * Feature: add SetEnabledMapperTag to set enabled flag for 'Mapper' tag check * Feature: add SetEnabledJsonTag to set enabled flag for 'Json' tag check