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,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…
Reference in New Issue