You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ntool/nmapper/mapper_object.go

685 lines
18 KiB
Go

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
}