feat: 添加工具包nmapper, 用于将struct与map互转,目前重点是strMap

main v1.1.9
NoahLan 1 year ago
parent d4a936cb17
commit 83920bfb2b

@ -0,0 +1,6 @@
package nmapper
const (
mapperTagKey = "mapper"
jsonTagKey = "json"
)

@ -0,0 +1,62 @@
package nmapper
import "git.noahlan.cn/noahlan/ntool/nmapper/wrapper"
var defaultMapper *MapperObject
type Option func(m *MapperObject)
func init() {
defaultMapper = NewMapper()
}
// SetCustomTag 为默认的Mapper设置自定义tag
func SetCustomTag(tag string) {
defaultMapper.customTagName = tag
}
// SetTypeCheck 为默认的Mapper设置开启类型检查
func SetTypeCheck(enabled bool) {
defaultMapper.enabledTypeChecking = enabled
}
// SetTypeWrapper 为默认的Mapper设置类型转换器
func SetTypeWrapper(wrappers ...wrapper.TypeWrapper) {
defaultMapper.SetTypeWrapper(wrappers...)
}
// WithCustomTag 设置自定义 tag
func WithCustomTag(tag string) Option {
return func(m *MapperObject) {
m.customTagName = tag
}
}
// WithTypeCheck 开启类型安全
func WithTypeCheck() Option {
return func(m *MapperObject) {
m.enabledTypeChecking = true
}
}
// WithTypeWrapper 设置类型转换器
func WithTypeWrapper(wrappers ...wrapper.TypeWrapper) Option {
return func(m *MapperObject) {
m.SetTypeWrapper(wrappers...)
}
}
func Mapper(dst any, src any) error {
return defaultMapper.Mapper(dst, src)
}
func ParseMap(dst any, src map[string]any) error {
return defaultMapper.ParseMap(dst, src)
}
func ParseStrMap(dst any, src map[string]string) error {
defaultMapper.SetTypeWrapper(wrapper.StrWrapper()...)
defer defaultMapper.SetTypeWrapper()
return defaultMapper.ParseStrMap(dst, src)
}

@ -0,0 +1,265 @@
package nmapper
import (
"errors"
"git.noahlan.cn/noahlan/ntool/internal/convert"
"git.noahlan.cn/noahlan/ntool/nmap"
"git.noahlan.cn/noahlan/ntool/nmapper/wrapper"
"git.noahlan.cn/noahlan/ntool/nmath"
"git.noahlan.cn/noahlan/ntool/nreflect"
"git.noahlan.cn/noahlan/ntool/nstr"
"git.noahlan.cn/noahlan/ntool/nstruct"
"reflect"
"time"
)
type MapperObject struct {
ZeroValue reflect.Value
typeWrappers []wrapper.TypeWrapper
timeType reflect.Type // 时间类型
enabledTypeChecking bool // 是否开启类型检查
customTagName string // 自定义Tag名称
}
func NewMapper(opts ...Option) *MapperObject {
tmp := &MapperObject{
ZeroValue: reflect.Value{},
timeType: reflect.TypeOf(time.Time{}),
typeWrappers: make([]wrapper.TypeWrapper, 0),
enabledTypeChecking: false,
customTagName: "",
}
for _, opt := range opts {
opt(tmp)
}
return tmp
}
func (m *MapperObject) SetTypeWrapper(wrappers ...wrapper.TypeWrapper) {
m.typeWrappers = wrappers
}
func (m *MapperObject) RegisterTypeWrapper(wrappers ...wrapper.TypeWrapper) {
m.typeWrappers = append(m.typeWrappers, wrappers...)
}
// MapperObject 将src转换为dst
// src为struct类型, dst为map类型 map[string]any 或者 map[string]string
func (m *MapperObject) Mapper(dst any, src any) error {
srcElem := reflect.ValueOf(src)
srcElem = nreflect.Indirect(srcElem)
dstElem := reflect.ValueOf(dst)
dstElem = nreflect.Indirect(dstElem)
if srcElem == m.ZeroValue {
return errors.New("src is not legal value")
}
if dstElem == m.ZeroValue {
return errors.New("dst is not legal value")
}
return m.elemMapper(dstElem, srcElem)
}
func (m *MapperObject) elemMapper(dstElem, srcElem reflect.Value) error {
//if dstElem.Type().Kind() != reflect.Map {
// return errors.New(fmt.Sprintf("dst is not legal value, need map but got %s", dstElem.Type().Kind().String()))
//}
if dstElem.Type().Kind() == reflect.Map {
m.elemToMap(dstElem, srcElem)
}
return nil
}
func (m *MapperObject) elemToMap(dstElem, srcElem reflect.Value) {
for i := 0; i < srcElem.NumField(); i++ {
srcFieldInfo := srcElem.Field(i)
// tag
name, tagMap := m.getFieldNameAndMap(srcElem, i)
if name == "" {
continue
}
valElem := srcFieldInfo
for _, w := range m.typeWrappers {
if w.Supported(srcFieldInfo) {
valElem = w.Wrap(srcFieldInfo, i, tagMap)
break
}
}
dstElem.SetMapIndex(reflect.ValueOf(name), valElem)
}
}
func (m *MapperObject) ParseMap(dst any, src map[string]any) error {
dstElem := reflect.ValueOf(dst)
dstElem = nreflect.Indirect(dstElem)
if dstElem == m.ZeroValue {
return errors.New("dst is not legal value")
}
for i := 0; i < dstElem.NumField(); i++ {
dstFieldInfo := dstElem.Field(i)
name, tagMap := m.getFieldNameAndMap(dstElem, i)
if name == "" {
continue
}
m.setFieldValue(dstFieldInfo, tagMap, src[name])
}
return nil
}
func (m *MapperObject) ParseStrMap(dst any, src map[string]string) error {
dstElem := reflect.ValueOf(dst)
dstElem = nreflect.Indirect(dstElem)
if dstElem == m.ZeroValue {
return errors.New("dst is not legal value")
}
for i := 0; i < dstElem.NumField(); i++ {
dstFieldInfo := dstElem.Field(i)
name, tagMap := m.getFieldNameAndMap(dstElem, i)
if name == "" {
continue
}
m.setFieldValue(dstFieldInfo, tagMap, src[name])
}
return nil
}
func (m *MapperObject) setFieldValue(fieldElem reflect.Value, tagMap nmap.SMap, value any) {
fieldKind := fieldElem.Type().Kind()
switch fieldKind {
case reflect.Bool:
if value == nil {
fieldElem.SetBool(false)
} else if v, ok := value.(bool); ok {
fieldElem.SetBool(v)
} else {
b, _ := convert.StrToBool(nstr.SafeString(value))
fieldElem.SetBool(b)
}
case reflect.String:
if value == nil {
fieldElem.SetString("")
} else {
fieldElem.SetString(nstr.SafeString(value))
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if value == nil {
fieldElem.SetInt(0)
} else {
val := reflect.ValueOf(value)
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fieldElem.SetInt(val.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
fieldElem.SetInt(int64(val.Uint()))
default:
v, _ := nmath.ToInt64(value)
fieldElem.SetInt(v)
}
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if value == nil {
fieldElem.SetUint(0)
} else {
val := reflect.ValueOf(value)
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fieldElem.SetUint(uint64(val.Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
fieldElem.SetUint(val.Uint())
default:
v, _ := nmath.ToUint(value)
fieldElem.SetUint(v)
}
}
case reflect.Float64, reflect.Float32:
if value == nil {
fieldElem.SetFloat(0)
} else {
val := reflect.ValueOf(value)
switch val.Kind() {
case reflect.Float64:
fieldElem.SetFloat(val.Float())
default:
fieldElem.SetFloat(nmath.QuietFloat(value))
}
}
case reflect.Struct:
if value == nil {
fieldElem.Set(reflect.Zero(fieldElem.Type()))
} else {
for _, w := range m.typeWrappers {
if w.Supported(fieldElem) {
w.Set(fieldElem, value, tagMap)
break
}
}
if reflect.ValueOf(value).Type() == fieldElem.Type() {
fieldElem.Set(reflect.ValueOf(value))
}
}
default:
if !fieldElem.IsValid() {
fieldElem.Set(reflect.Zero(fieldElem.Type()))
return
}
// Type check
if reflect.ValueOf(value).Type() == fieldElem.Type() {
fieldElem.Set(reflect.ValueOf(value))
}
}
}
// getFieldNameAndMap 获取字段名称和tagMap
func (m *MapperObject) getFieldNameAndMap(objElem reflect.Value, index int) (string, nmap.SMap) {
field := objElem.Type().Field(index)
// 1. custom
tagVal, ok := field.Tag.Lookup(m.customTagName)
if ok && tagVal != "" {
sMap, _ := nstruct.ParseTagValueDefault(field.Name, tagVal)
name := sMap.Get("name")
if name != "" {
return name, sMap
} else {
// tag "-"
return "", nil
}
}
// 2. mapper
tagVal, ok = field.Tag.Lookup(mapperTagKey)
if ok && tagVal != "" {
sMap, _ := nstruct.ParseTagValueDefault(field.Name, tagVal)
name := sMap.Get("name")
if name != "" {
return name, sMap
} else {
// tag "-"
return "", nil
}
}
// 3. json
tagVal, ok = field.Tag.Lookup(jsonTagKey)
if ok && tagVal != "" {
sMap, _ := nstruct.ParseTagValueDefault(field.Name, tagVal)
name := sMap.Get("name")
if name != "" {
return name, sMap
} else {
// tag "-"
return "", nil
}
}
return field.Name, nmap.SMap{"name": field.Name}
}

@ -0,0 +1,89 @@
package nmapper_test
import (
"fmt"
"git.noahlan.cn/noahlan/ntool/nmapper"
"git.noahlan.cn/noahlan/ntool/nmapper/wrapper"
"testing"
"time"
)
type testType struct {
Int int `json:"int"`
Int8 int8 `json:"int8"`
UInt8 uint8 `json:"uint8"`
Float32 float32 `json:"float32"`
Float64 float64 `json:"float64"`
String string `json:"string"`
Time time.Time `json:"time,layout=2006-01-02"`
TimeNoLayout time.Time `json:"time_no_layout"`
Ignore int `json:"-"`
}
func TestMapper_Mapper(t *testing.T) {
mapper := nmapper.NewMapper()
dest := make(map[string]any)
src := &testType{
Int: 1,
Int8: 2,
UInt8: 3,
Float32: 4.2,
Float64: 5.2222222222,
String: "test",
Time: time.Now(),
TimeNoLayout: time.Now().Add(time.Hour * 24),
Ignore: 99,
}
_ = mapper.Mapper(dest, src)
dest2 := make(map[string]string)
mapper.SetTypeWrapper(wrapper.StrWrapper()...)
_ = mapper.Mapper(dest2, src)
mapper.SetTypeWrapper()
fmt.Printf("dest1:\n%+v\n", dest)
fmt.Printf("dest2:\n%+v\n", dest2)
}
func TestMapper_Parse(t *testing.T) {
src := make(map[string]any)
src["int"] = 1
src["int8"] = 2
src["uint8"] = 3
src["float32"] = 4.2
src["float64"] = 5.2222222222
src["string"] = "test"
src["time"] = time.Now()
src["time_no_layout"] = time.Now().Add(time.Hour * 24)
src["ignore"] = "ignore"
var dest testType
mapper := nmapper.NewMapper()
_ = mapper.ParseMap(&dest, src)
fmt.Printf("%+v\n", dest)
}
func TestMapperObject_ParseStrMap(t *testing.T) {
src := make(map[string]string)
src["int"] = "1"
src["int8"] = "2"
src["uint8"] = "3"
src["float32"] = "4.2"
src["float64"] = "5.2222222222"
src["string"] = "test"
src["time"] = "2023-08-18 10:07:40"
src["time_no_layout"] = "2023-08-19 10:07:40"
src["ignore"] = "ignore"
var dest testType
mapper := nmapper.NewMapper()
mapper.SetTypeWrapper(wrapper.StrWrapper()...)
_ = mapper.ParseStrMap(&dest, src)
fmt.Printf("%+v\n", dest)
}

@ -0,0 +1,41 @@
package wrapper
import (
"git.noahlan.cn/noahlan/ntool/nmap"
"git.noahlan.cn/noahlan/ntool/nreflect"
"reflect"
"time"
)
// StrCommonWrapper 将普通字段转换为string
// 支持如下类型
// reflect.Invalid -> ""
// reflect.Bool -> "true", "false"
// reflect.String -> "str"
// reflect.Float32,reflect.Float64 -> "x.x"
// reflect.IntX -> "1"
// reflect.UintX -> "1"
// 非以上类型的使用fmt.Sprint进行格式化
type StrCommonWrapper struct {
BaseTypeWrapper
}
func NewStrCommonWrapper() TypeWrapper {
return &StrCommonWrapper{}
}
func (*StrCommonWrapper) Supported(value reflect.Value) bool {
// 不对time类型做处理
if _, ok := value.Interface().(time.Time); ok {
return false
}
return true
}
func (*StrCommonWrapper) Wrap(valueElem reflect.Value, index int, tagMap nmap.SMap) reflect.Value {
return reflect.ValueOf(nreflect.String(valueElem))
}
func (t *StrCommonWrapper) Set(fieldElem reflect.Value, value any, tagMap nmap.SMap) {
// Set是仅针对struct的情况
}

@ -0,0 +1,55 @@
package wrapper
import (
"git.noahlan.cn/noahlan/ntool/nmap"
"git.noahlan.cn/noahlan/ntool/ntime"
"reflect"
"time"
)
// StrTimeWrapper Time->string | string->Time
type StrTimeWrapper struct {
BaseTypeWrapper
timeType reflect.Type
}
func NewStrTimeWrapper() TypeWrapper {
return &StrTimeWrapper{
timeType: reflect.TypeOf(time.Time{}),
}
}
func (*StrTimeWrapper) Supported(value reflect.Value) bool {
if _, ok := value.Interface().(time.Time); ok {
return true
}
return false
}
func (*StrTimeWrapper) Wrap(valueElem reflect.Value, index int, tagMap nmap.SMap) reflect.Value {
layout := tagMap.Default("layout", time.DateTime)
t := valueElem.Interface().(time.Time)
return reflect.ValueOf(t.Format(layout))
}
func (t *StrTimeWrapper) Set(fieldElem reflect.Value, value any, tagMap nmap.SMap) {
layout := tagMap.Default("layout", time.DateTime)
var timeStr string
switch d := value.(type) {
case []byte:
timeStr = string(d)
case string:
timeStr = d
case int64:
fieldElem.Set(reflect.ValueOf(time.Unix(d, 0)))
case time.Time:
if fieldElem.Type() == t.timeType {
timeStr = ""
fieldElem.Set(reflect.ValueOf(value))
}
}
if timeStr != "" && fieldElem.Type() == t.timeType {
t, _ := ntime.ParseTime(timeStr, layout)
fieldElem.Set(reflect.ValueOf(t))
}
}

@ -0,0 +1,8 @@
package wrapper
func StrWrapper() []TypeWrapper {
return []TypeWrapper{
NewStrCommonWrapper(),
NewStrTimeWrapper(),
}
}

@ -0,0 +1,32 @@
package wrapper
import (
"git.noahlan.cn/noahlan/ntool/nmap"
"reflect"
)
type TypeWrapper interface {
// Supported 转换器是否支持对应属性类型
Supported(value reflect.Value) bool
// Wrap 将对应属性转换为需要类型的值
Wrap(valueElem reflect.Value, index int, tagMap nmap.SMap) reflect.Value
// Set 设置对应属性值
Set(fieldElem reflect.Value, value any, tagMap nmap.SMap)
}
var _ TypeWrapper = (*BaseTypeWrapper)(nil)
type BaseTypeWrapper struct {
}
func (w *BaseTypeWrapper) Supported(value reflect.Value) bool {
return false
}
func (w *BaseTypeWrapper) Wrap(valueElem reflect.Value, index int, tagMap nmap.SMap) reflect.Value {
panic("implement me")
}
func (w *BaseTypeWrapper) Set(fieldElem reflect.Value, value any, tagMap nmap.SMap) {
fieldElem.Set(reflect.ValueOf(value))
}
Loading…
Cancel
Save