refactor: 优化修复nmapper工具,现在支持嵌套struct,指针struct等等

main v1.1.10
NoahLan 9 months ago
parent a66fe300cd
commit 2f4f631390

@ -1,6 +1,9 @@
package nmapper
import "git.noahlan.cn/noahlan/ntool/ndef"
const (
mapperTagKey = "mapper"
jsonTagKey = "json"
mapperTagKey = "mapper"
jsonTagKey = "json"
flattenSeparator = ndef.DotStr
)

@ -46,17 +46,17 @@ func WithTypeWrapper(wrappers ...wrapper.TypeWrapper) Option {
}
}
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)
}
//func Mapper(dst any, src any) error {
// return defaultMapper.Mapper(dst, src)
//}
//
//func ParseMap(dst any, src map[string]any) error {
// return defaultMapper.Parse(dst, src)
//}
//
//func ParseStrMap(dst any, src map[string]string) error {
// defaultMapper.SetTypeWrapper(wrapper.StrWrapper()...)
// defer defaultMapper.SetTypeWrapper()
//
// return defaultMapper.Parse(dst, src)
//}

@ -2,6 +2,7 @@ package nmapper
import (
"errors"
"fmt"
"git.noahlan.cn/noahlan/ntool/internal/convert"
"git.noahlan.cn/noahlan/ntool/nmap"
"git.noahlan.cn/noahlan/ntool/nmapper/wrapper"
@ -10,18 +11,36 @@ import (
"git.noahlan.cn/noahlan/ntool/nstr"
"git.noahlan.cn/noahlan/ntool/nstruct"
"reflect"
"strings"
"sync"
"time"
)
type MapperObject struct {
ZeroValue reflect.Value
type (
field struct {
name string // 原始名
tagName string
index []int // 索引列表,层级
tagMap nmap.SMap // tag Map
omitEmpty bool // 空值忽略
typ reflect.Type
}
structFields struct {
list []field
nameIndex map[string]int
}
typeWrappers []wrapper.TypeWrapper
timeType reflect.Type // 时间类型
enabledTypeChecking bool // 是否开启类型检查
MapperObject struct {
ZeroValue reflect.Value
customTagName string // 自定义Tag名称
}
typeWrappers []wrapper.TypeWrapper
timeType reflect.Type // 时间类型
enabledTypeChecking bool // 是否开启类型检查
customTagName string // 自定义Tag名称
}
)
func NewMapper(opts ...Option) *MapperObject {
tmp := &MapperObject{
@ -47,7 +66,7 @@ func (m *MapperObject) RegisterTypeWrapper(wrappers ...wrapper.TypeWrapper) {
m.typeWrappers = append(m.typeWrappers, wrappers...)
}
// MapperObject 将src转换为dst
// Mapper 将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)
@ -59,83 +78,334 @@ func (m *MapperObject) Mapper(dst any, src any) error {
if srcElem == m.ZeroValue {
return errors.New("src is not legal value")
}
if srcElem.Kind() != reflect.Struct {
return errors.New("src must be struct")
}
if dstElem == m.ZeroValue {
return errors.New("dst is not legal value")
}
return m.elemMapper(dstElem, srcElem)
if dstElem.Type().Kind() != reflect.Map {
return errors.New("dst is not map")
}
flatten := dstElem.Type().Elem().Kind() == reflect.String
if flatten {
m.SetTypeWrapper(wrapper.StrWrapper()...)
defer func() {
m.SetTypeWrapper()
}()
}
m.elemToMap(dstElem, srcElem, "", flatten)
return nil
}
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)
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Pointer:
return v.IsNil()
}
return nil
return false
}
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 == "" {
func (m *MapperObject) elemToMap(dstElem, srcElem reflect.Value, preKey string, flatten bool) {
fields := cachedTypeFields(srcElem.Type(), m.customTagName)
FieldLoop:
for i := range fields.list {
f := &fields.list[i]
fv := srcElem
for _, i := range f.index {
if fv.Kind() == reflect.Ptr {
if fv.IsNil() {
continue FieldLoop
}
fv = fv.Elem()
}
fv = fv.Field(i)
}
if f.omitEmpty && isEmptyValue(fv) {
continue
}
valElem := srcFieldInfo
for _, w := range m.typeWrappers {
if w.Supported(srcFieldInfo) {
valElem = w.Wrap(srcFieldInfo, i, tagMap)
break
fv = nreflect.IndirectAddr(fv)
if fv.Kind() == reflect.Struct && fv.Type().Name() != "Time" {
if flatten {
m.elemToMap(dstElem, fv, f.tagName, flatten)
} else {
tmpMapV := reflect.ValueOf(make(map[string]any))
dstElem.SetMapIndex(reflect.ValueOf(f.tagName), tmpMapV)
m.elemToMap(tmpMapV, fv, f.tagName, flatten)
}
} else {
wrappedFv := fv
for _, w := range m.typeWrappers {
if w.Supported(wrappedFv) {
wrappedFv = w.Wrap(wrappedFv, i, f.tagMap)
break
}
}
key := reflect.ValueOf(f.tagName)
if flatten && preKey != "" {
key = reflect.ValueOf(preKey + flattenSeparator + f.tagName)
}
dstElem.SetMapIndex(key, wrappedFv)
}
dstElem.SetMapIndex(reflect.ValueOf(name), valElem)
}
}
func (m *MapperObject) ParseMap(dst any, src map[string]any) error {
func (m *MapperObject) Parse(dst any, src any, flatten bool) error {
srcElem := reflect.ValueOf(src)
if srcElem.Type().Kind() != reflect.Map {
return errors.New("src is not legal value, must be map[string]any or map[string]string")
}
//flatten := srcElem.Type().Elem().Kind() == reflect.String
if flatten {
m.SetTypeWrapper(wrapper.StrWrapper()...)
defer func() {
m.SetTypeWrapper()
}()
}
dstElem := reflect.ValueOf(dst)
dstElem = nreflect.Indirect(dstElem)
if dstElem.Kind() != reflect.Pointer || dstElem.IsNil() {
return errors.New("non-pointer dst")
}
if dstElem == m.ZeroValue {
return errors.New("dst is not legal value")
return m.parseInner(dstElem, srcElem, flatten)
}
func (m *MapperObject) parseInner(v reflect.Value, srcElem reflect.Value, flatten bool) error {
pv := nreflect.IndirectAddr(v)
v = pv
t := v.Type()
// Decoding into nil interface? Switch to non-reflect code.
if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
v.Set(srcElem)
return nil
}
var fields structFields
// Check type of target:
// struct or
// map[T1]T2 where T1 is string
switch v.Kind() {
case reflect.Map:
// Map key must either have string kind
switch t.Key().Kind() {
case reflect.String:
default:
//return errors.New("the type of key must be string")
return nil
}
if v.IsNil() {
v.Set(reflect.MakeMap(t))
}
case reflect.Struct:
fields = cachedTypeFields(t, m.customTagName)
// ok
default:
return errors.New("type of dst must be map[string]xx or struct")
}
for i := 0; i < dstElem.NumField(); i++ {
dstFieldInfo := dstElem.Field(i)
name, tagMap := m.getFieldNameAndMap(dstElem, i)
if name == "" {
var mapElem reflect.Value
mapIter := srcElem.MapRange()
for mapIter.Next() {
mapKey, mapValue := mapIter.Key(), mapIter.Value()
if !mapValue.IsValid() || mapValue.IsZero() {
continue
}
m.setFieldValue(dstFieldInfo, tagMap, src[name])
}
return nil
}
// key
key := mapKey.String()
func (m *MapperObject) ParseStrMap(dst any, src map[string]string) error {
dstElem := reflect.ValueOf(dst)
dstElem = nreflect.Indirect(dstElem)
// Figure out field corresponding to key.
var (
subv reflect.Value
tagMap nmap.SMap
)
if v.Kind() == reflect.Map {
elemType := t.Elem()
if !mapElem.IsValid() {
mapElem = reflect.New(elemType).Elem()
} else {
mapElem.Set(reflect.Zero(elemType))
}
subv = mapElem
} else {
var f *field
// 对于key中包含分隔符
if strings.Contains(mapKey.String(), flattenSeparator) {
keys := nstr.Split(mapKey.String(), flattenSeparator)
keyLen := len(keys)
var tmpFields = fields
for i := 0; i < keyLen; i++ {
k := keys[i]
var tmpF *field
if i, ok := tmpFields.nameIndex[k]; ok {
// Found an exact name match.
tmpF = &tmpFields.list[i]
} else {
// Fall back to the expensive case-insensitive
// linear search.
for i := range tmpFields.list {
ff := &tmpFields.list[i]
if ff.tagName == k {
tmpF = ff
break
}
}
}
if tmpF != nil {
f = tmpF
if f.typ.Kind() == reflect.Struct {
if i < keyLen-1 {
if flatten {
// 扁平化模式下需要将subv指定为倒数第二个key所指向的struct
subv = m.findRValueByField(subv, v, f)
}
if f.typ.Kind() == reflect.Struct {
tmpFields = cachedTypeFields(f.typ, m.customTagName)
}
}
}
} else {
// 当未能发现对应key的指向时需判断上层是否为map所为map则需要构建
if f != nil && f.typ.Kind() == reflect.Map {
// 此时的f为上层field
if subv.Kind() != reflect.Map {
subv = m.findRValueByField(subv, v, f)
}
if subv.Kind() == reflect.Map {
if subv.IsNil() {
subv.Set(reflect.MakeMap(f.typ))
}
if i < keyLen-1 {
subvTElem := subv.Type().Elem()
if subvTElem.Kind() == reflect.Interface || subvTElem.Kind() == reflect.Map {
tmpMap := reflect.MakeMap(f.typ)
subv.SetMapIndex(reflect.ValueOf(k), tmpMap)
subv = tmpMap
}
}
if i == keyLen-1 {
mapKey = reflect.ValueOf(k)
}
}
}
if i == keyLen-1 {
f = nil
}
}
}
} else {
if i, ok := fields.nameIndex[key]; ok {
// Found an exact name match.
f = &fields.list[i]
} else {
// Fall back to the expensive case-insensitive
// linear search.
for i := range fields.list {
ff := &fields.list[i]
if ff.tagName == key {
f = ff
break
}
}
}
}
if dstElem == m.ZeroValue {
return errors.New("dst is not legal value")
}
if f != nil {
tagMap = f.tagMap
subv = m.findRValueByField(subv, v, f)
}
}
for i := 0; i < dstElem.NumField(); i++ {
dstFieldInfo := dstElem.Field(i)
name, tagMap := m.getFieldNameAndMap(dstElem, i)
if name == "" {
if !subv.IsValid() {
continue
}
m.setFieldValue(dstFieldInfo, tagMap, src[name])
subv = nreflect.IndirectAddr(subv)
switch subv.Kind() {
case reflect.Map:
if subv.IsNil() {
subv.Set(reflect.MakeMap(subv.Type()))
}
if mapValue.Kind() == reflect.Interface && mapValue.Elem().Kind() == reflect.Map {
subv.Set(mapValue.Elem())
} else {
subv.SetMapIndex(mapKey, mapValue)
}
case reflect.Struct:
// 递归将mapValue作为本次的src进行传递
if mapValue.Kind() == reflect.Interface && mapValue.Elem().Kind() == reflect.Map {
if err := m.parseInner(subv, reflect.ValueOf(mapValue.Interface()), false); err != nil {
return err
}
} else {
m.setFieldValue(subv, tagMap, mapValue.Interface())
}
default:
// 普通属性,直接设置值
m.setFieldValue(subv, tagMap, mapValue.Interface())
}
// 递归回溯后
// Write value back to map;
// if using struct, subv points into struct already.
if v.Kind() == reflect.Map {
kt := t.Key()
var kv reflect.Value
switch {
case kt.Kind() == reflect.String:
kv = reflect.ValueOf(key).Convert(kt)
default:
return fmt.Errorf("unexpected key type")
}
if kv.IsValid() {
v.SetMapIndex(kv, subv)
}
}
}
return nil
}
func (m *MapperObject) findRValueByField(subv reflect.Value, topv reflect.Value, f *field) reflect.Value {
if !subv.IsValid() {
subv = topv
}
for _, i := range f.index {
if subv.Kind() == reflect.Ptr {
if subv.IsNil() {
// If a struct embeds a pointer to an unexported type,
// it is not possible to set a newly allocated value
// since the field is unexported.
//
// See https://golang.org/issue/21357
if !subv.CanSet() {
//return fmt.Errorf("cannot set embedded pointer to unexported struct: %v", subv.Type().Elem())
break
}
subv.Set(reflect.New(subv.Type().Elem()))
}
subv = subv.Elem()
}
subv = subv.Field(i)
}
return subv
}
func (m *MapperObject) setFieldValue(fieldElem reflect.Value, tagMap nmap.SMap, value any) {
fieldKind := fieldElem.Type().Kind()
//v := nreflect.IndirectAddr(fieldElem)
switch fieldKind {
case reflect.Bool:
@ -222,10 +492,9 @@ func (m *MapperObject) setFieldValue(fieldElem reflect.Value, tagMap nmap.SMap,
}
// getFieldNameAndMap 获取字段名称和tagMap
func (m *MapperObject) getFieldNameAndMap(objElem reflect.Value, index int) (string, nmap.SMap) {
field := objElem.Type().Field(index)
func getFieldNameAndMap(field reflect.StructField, customTagName string) (string, nmap.SMap) {
// 1. custom
tagVal, ok := field.Tag.Lookup(m.customTagName)
tagVal, ok := field.Tag.Lookup(customTagName)
if ok && tagVal != "" {
sMap, _ := nstruct.ParseTagValueDefault(field.Name, tagVal)
name := sMap.Get("name")
@ -263,3 +532,153 @@ func (m *MapperObject) getFieldNameAndMap(objElem reflect.Value, index int) (str
}
return field.Name, nmap.SMap{"name": field.Name}
}
var fieldCache sync.Map
func cachedTypeFields(t reflect.Type, customTagName string) structFields {
if f, ok := fieldCache.Load(t); ok {
return f.(structFields)
}
f, _ := fieldCache.LoadOrStore(t, typeFields(t, customTagName))
return f.(structFields)
}
// typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func typeFields(t reflect.Type, customTagName string) structFields {
// Anonymous fields to explore at the current level and the next.
var current []field
next := []field{{typ: t}}
// Count of queued names for current level and the next.
var count, nextCount map[reflect.Type]int
// Types already visited at an earlier level.
visited := map[reflect.Type]bool{}
// Fields found.
var fields []field
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[reflect.Type]int{}
for _, f := range current {
if visited[f.typ] {
continue
}
visited[f.typ] = true
// Scan f.typ for fields to include.
for i := 0; i < f.typ.NumField(); i++ {
sf := f.typ.Field(i)
if sf.Anonymous {
t := sf.Type
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if !sf.IsExported() && t.Kind() != reflect.Struct {
// Ignore embedded fields of unexported non-struct types.
continue
}
// Do not ignore embedded fields of unexported struct types
// since they may have exported fields.
} else if !sf.IsExported() {
// Ignore unexported non-embedded fields.
continue
}
name, tagMap := getFieldNameAndMap(sf, customTagName)
if name == "" {
continue
}
index := make([]int, len(f.index)+1)
copy(index, f.index)
index[len(f.index)] = i
ft := sf.Type
if ft.Name() == "" && ft.Kind() == reflect.Pointer {
// Follow pointer.
ft = ft.Elem()
}
// Record found field and index sequence.
if !sf.Anonymous || ft.Kind() != reflect.Struct {
fields = append(fields, field{
name: sf.Name,
tagName: name,
index: index,
tagMap: tagMap,
omitEmpty: tagMap.Has("omitempty"),
typ: ft,
})
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
// It only cares about the distinction between 1 or 2,
// so don't bother generating any more copies.
fields = append(fields, fields[len(fields)-1])
}
continue
}
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
next = append(next, field{name: ft.Name(), index: index, typ: ft})
}
}
}
}
// Delete all fields that are hidden by the Go rules for embedded fields,
// except that fields with JSON tags are promoted.
// The fields are sorted in primary order of name, secondary order
// of field index length. Loop over names; for each name, delete
// hidden fields by choosing the one dominant field that survives.
out := fields[:0]
for advance, i := 0, 0; i < len(fields); i += advance {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.name
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.name != name {
break
}
}
if advance == 1 { // Only one field with this name
out = append(out, fi)
continue
}
dominant, ok := dominantField(fields[i : i+advance])
if ok {
out = append(out, dominant)
}
}
fields = out
nameIndex := make(map[string]int, len(fields))
for i, field := range fields {
nameIndex[field.tagName] = i
}
return structFields{fields, nameIndex}
}
// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's embedding rules, modified by the presence of
// JSON tags. If there are multiple top-level fields, the boolean
// will be false: This condition is an error in Go and we skip all
// the fields.
func dominantField(fields []field) (field, bool) {
// The fields are sorted in increasing index-length order, then by presence of tag.
// That means that the first field is the dominant one. We need only check
// for error cases: two fields at top level, either both tagged or neither tagged.
if len(fields) > 1 && len(fields[0].index) == len(fields[1].index) {
return field{}, false
}
return fields[0], true
}

@ -4,10 +4,16 @@ import (
"fmt"
"git.noahlan.cn/noahlan/ntool/nmapper"
"git.noahlan.cn/noahlan/ntool/nmapper/wrapper"
"git.noahlan.cn/noahlan/ntool/nreflect"
"reflect"
"testing"
"time"
)
type testInnerType struct {
InInt int `json:"in_int"`
}
type testType struct {
Int int `json:"int"`
Int8 int8 `json:"int8"`
@ -18,6 +24,50 @@ type testType struct {
Time time.Time `json:"time,layout=2006-01-02"`
TimeNoLayout time.Time `json:"time_no_layout"`
Ignore int `json:"-"`
*testInnerType
TT *testInnerType `json:"tt"`
t *testInnerType `json:"t"`
}
func TestT(t *testing.T) {
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,
testInnerType: &testInnerType{InInt: 111},
TT: &testInnerType{InInt: 222},
t: &testInnerType{InInt: 333},
}
valElem := reflect.ValueOf(src)
valElem = nreflect.Indirect(valElem)
typElem := reflect.TypeOf(src)
typElem = typElem.Elem()
for i := 0; i < valElem.NumField(); i++ {
fieldValElem := valElem.Field(i)
fieldTypElem := typElem.Field(i)
fieldElem := fieldValElem.Type()
fmt.Println(fieldElem.Name(), fieldTypElem.Anonymous, fieldTypElem.Name)
fmt.Println(fieldElem.Kind(), fieldTypElem.Type)
fmt.Println(fieldElem.String(), fieldTypElem.IsExported())
fmt.Println()
//if fieldElem.Kind() == reflect.Ptr {
// fff := fieldElem.Elem()
// fmt.Println(fff.Name())
// fmt.Println(fff.Kind())
// fmt.Println(fff.String())
//}
}
}
func TestMapper_Mapper(t *testing.T) {
@ -25,22 +75,22 @@ func TestMapper_Mapper(t *testing.T) {
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,
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,
testInnerType: &testInnerType{InInt: 111},
TT: &testInnerType{InInt: 222},
}
_ = 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)
@ -61,7 +111,7 @@ func TestMapper_Parse(t *testing.T) {
var dest testType
mapper := nmapper.NewMapper()
_ = mapper.ParseMap(&dest, src)
_ = mapper.Parse(&dest, src, false)
fmt.Printf("%+v\n", dest)
}
@ -74,16 +124,85 @@ func TestMapperObject_ParseStrMap(t *testing.T) {
src["float32"] = "4.2"
src["float64"] = "5.2222222222"
src["string"] = "test"
src["time"] = "2023-08-18 10:07:40"
src["time"] = "2023-08-18"
src["time_no_layout"] = "2023-08-19 10:07:40"
src["ignore"] = "ignore"
var dest testType
mapper := nmapper.NewMapper()
_ = mapper.Parse(&dest, src, false)
fmt.Printf("%+v\n", dest)
}
type testFlattenInnerType struct {
A int `json:"a"`
C int `json:"c"`
}
type testFlattenInnerType2 struct {
B int `json:"b"`
AT testFlattenInnerType `json:"t"`
}
type testFlattenInnerType3 struct {
M3 map[string]string `json:"m3"`
}
type testFlattenType struct {
testFlattenInnerType
T *testFlattenInnerType `json:"t"`
TT *testFlattenInnerType `json:"tt"`
T2 testFlattenInnerType2 `json:"t2"`
T3 *testFlattenInnerType3 `json:"t3"`
M map[string]string `json:"m"`
M2 map[string]any `json:"m2"`
Int int `json:"int"`
Time time.Time `json:"time,layout=2006-01-02"`
}
func TestParseFlatten(t *testing.T) {
src := make(map[string]string)
src["a"] = "1"
src["c"] = "2"
src["t.a"] = "1"
src["tt.a"] = "11"
src["tt.c"] = "33"
src["t2.b"] = "2"
src["t2.t.a"] = "3"
src["t2.at.a"] = "4"
src["t3.m3.a"] = "a"
src["t3.m3.b"] = "b"
src["m.dd"] = "x.xx"
src["m2.a1.b1"] = "xx"
src["m2.a2.b2.c2"] = "xx"
src["int"] = "99"
src["time"] = "2010-09-18"
var dest testFlattenType
mapper := nmapper.NewMapper()
mapper.SetTypeWrapper(wrapper.StrWrapper()...)
_ = mapper.Parse(&dest, src, true)
fmt.Printf("%+v\n", dest)
}
func TestParse(t *testing.T) {
src := make(map[string]any)
src["t"] = map[string]any{"a": "1"}
src["tt"] = map[string]any{"a": "11", "c": "33"}
src["t2"] = map[string]any{"b": "2", "t": map[string]any{"a": "3"}, "at": map[string]any{"a": "4"}}
src["m"] = map[string]string{"dd": "x.xx"}
src["m2"] = map[string]any{"a1": map[string]any{"b1": "xx"}, "a2": map[string]any{"b2": map[string]any{"c2": "xx"}}}
src["int"] = "99"
src["time"] = "2010-09-18"
src["a"] = "1"
var dest testFlattenType
mapper := nmapper.NewMapper()
mapper.SetTypeWrapper(wrapper.StrWrapper()...)
_ = mapper.ParseStrMap(&dest, src)
_ = mapper.Parse(&dest, src, false)
fmt.Printf("%+v\n", dest)
}

Loading…
Cancel
Save