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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
}