|
|
|
|
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
|
|
|
|
|
}
|