diff --git a/nmapper/const.go b/nmapper/const.go index 2c86d4b..e49ac1b 100644 --- a/nmapper/const.go +++ b/nmapper/const.go @@ -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 ) diff --git a/nmapper/mapper.go b/nmapper/mapper.go index e424af7..08fca4a 100644 --- a/nmapper/mapper.go +++ b/nmapper/mapper.go @@ -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) +//} diff --git a/nmapper/mapper_object.go b/nmapper/mapper_object.go index c97c294..d09fec0 100644 --- a/nmapper/mapper_object.go +++ b/nmapper/mapper_object.go @@ -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 +} diff --git a/nmapper/mapper_object_test.go b/nmapper/mapper_object_test.go index 026b15c..3fcf49f 100644 --- a/nmapper/mapper_object_test.go +++ b/nmapper/mapper_object_test.go @@ -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) }