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" "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" "strings" "sync" "time" ) 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 } 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...) } // 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) 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 srcElem.Kind() != reflect.Struct { return errors.New("src must be struct") } if dstElem == m.ZeroValue { return errors.New("dst is not legal value") } 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 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 false } 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 } 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) } } } 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) if dstElem.Kind() != reflect.Pointer || dstElem.IsNil() { return errors.New("non-pointer dst") } 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") } var mapElem reflect.Value mapIter := srcElem.MapRange() for mapIter.Next() { mapKey, mapValue := mapIter.Key(), mapIter.Value() if !mapValue.IsValid() || mapValue.IsZero() { continue } // key key := mapKey.String() // 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 f != nil { tagMap = f.tagMap subv = m.findRValueByField(subv, v, f) } } if !subv.IsValid() { continue } 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: 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 getFieldNameAndMap(field reflect.StructField, customTagName string) (string, nmap.SMap) { // 1. custom tagVal, ok := field.Tag.Lookup(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} } 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 }