github.com/gogf/gf/v2@v2.7.4/util/gconv/internal/structcache/structcache_cached_struct_info.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  package structcache
     8  
     9  import (
    10  	"reflect"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/gogf/gf/v2/internal/utils"
    15  	"github.com/gogf/gf/v2/os/gtime"
    16  )
    17  
    18  // CachedStructInfo holds the cached info for certain struct.
    19  type CachedStructInfo struct {
    20  	// This map field is mainly used in the bindStructWithLoopParamsMap method
    21  	// key = field's name
    22  	// Will save all field names and PriorityTagAndFieldName
    23  	// for example:
    24  	//	field string `json:"name"`
    25  	//
    26  	// It will be stored twice, which keys are `name` and `field`.
    27  	tagOrFiledNameToFieldInfoMap map[string]*CachedFieldInfo
    28  
    29  	// All sub attributes field info slice.
    30  	FieldConvertInfos []*CachedFieldInfo
    31  }
    32  
    33  func (csi *CachedStructInfo) HasNoFields() bool {
    34  	return len(csi.tagOrFiledNameToFieldInfoMap) == 0
    35  }
    36  
    37  func (csi *CachedStructInfo) GetFieldInfo(fieldName string) *CachedFieldInfo {
    38  	return csi.tagOrFiledNameToFieldInfoMap[fieldName]
    39  }
    40  
    41  func (csi *CachedStructInfo) AddField(field reflect.StructField, fieldIndexes []int, priorityTags []string) {
    42  	alreadyExistFieldInfo, ok := csi.tagOrFiledNameToFieldInfoMap[field.Name]
    43  	if !ok {
    44  		cachedFieldInfo := csi.makeCachedFieldInfo(field, fieldIndexes, priorityTags)
    45  		for _, tagOrFieldName := range cachedFieldInfo.PriorityTagAndFieldName {
    46  			newFieldInfo := &CachedFieldInfo{
    47  				CachedFieldInfoBase: cachedFieldInfo.CachedFieldInfoBase,
    48  				IsField:             tagOrFieldName == field.Name,
    49  			}
    50  			csi.tagOrFiledNameToFieldInfoMap[tagOrFieldName] = newFieldInfo
    51  			if newFieldInfo.IsField {
    52  				csi.FieldConvertInfos = append(csi.FieldConvertInfos, newFieldInfo)
    53  			}
    54  		}
    55  		return
    56  	}
    57  	// If the field name and type are the same
    58  	if alreadyExistFieldInfo.StructField.Type == field.Type {
    59  		alreadyExistFieldInfo.OtherSameNameField = append(
    60  			alreadyExistFieldInfo.OtherSameNameField,
    61  			csi.copyCachedInfoWithFieldIndexes(alreadyExistFieldInfo, fieldIndexes),
    62  		)
    63  		return
    64  	}
    65  	// If the types are different, some information needs to be reset
    66  	alreadyExistFieldInfo.OtherSameNameField = append(
    67  		alreadyExistFieldInfo.OtherSameNameField,
    68  		csi.makeCachedFieldInfo(field, fieldIndexes, priorityTags),
    69  	)
    70  }
    71  
    72  // copyCachedInfoWithFieldIndexes copies and returns a new CachedFieldInfo based on given CachedFieldInfo, but different
    73  // FieldIndexes. Mainly used for copying fields with the same name and type.
    74  func (csi *CachedStructInfo) copyCachedInfoWithFieldIndexes(cfi *CachedFieldInfo, fieldIndexes []int) *CachedFieldInfo {
    75  	base := CachedFieldInfoBase{}
    76  	base = *cfi.CachedFieldInfoBase
    77  	base.FieldIndexes = fieldIndexes
    78  	return &CachedFieldInfo{
    79  		CachedFieldInfoBase: &base,
    80  	}
    81  }
    82  
    83  func (csi *CachedStructInfo) makeCachedFieldInfo(
    84  	field reflect.StructField, fieldIndexes []int, priorityTags []string,
    85  ) *CachedFieldInfo {
    86  	base := &CachedFieldInfoBase{
    87  		IsCommonInterface:       checkTypeIsCommonInterface(field),
    88  		StructField:             field,
    89  		FieldIndexes:            fieldIndexes,
    90  		ConvertFunc:             csi.genFieldConvertFunc(field.Type.String()),
    91  		IsCustomConvert:         csi.checkTypeHasCustomConvert(field.Type),
    92  		PriorityTagAndFieldName: csi.genPriorityTagAndFieldName(field, priorityTags),
    93  		RemoveSymbolsFieldName:  utils.RemoveSymbols(field.Name),
    94  	}
    95  	base.LastFuzzyKey.Store(field.Name)
    96  	return &CachedFieldInfo{
    97  		CachedFieldInfoBase: base,
    98  	}
    99  }
   100  
   101  func (csi *CachedStructInfo) genFieldConvertFunc(fieldType string) (convertFunc func(from any, to reflect.Value)) {
   102  	if fieldType[0] == '*' {
   103  		convertFunc = csi.genFieldConvertFunc(fieldType[1:])
   104  		if convertFunc == nil {
   105  			return nil
   106  		}
   107  		return csi.genPtrConvertFunc(convertFunc)
   108  	}
   109  	switch fieldType {
   110  	case "int", "int8", "int16", "int32", "int64":
   111  		convertFunc = func(from any, to reflect.Value) {
   112  			to.SetInt(localCommonConverter.Int64(from))
   113  		}
   114  	case "uint", "uint8", "uint16", "uint32", "uint64":
   115  		convertFunc = func(from any, to reflect.Value) {
   116  			to.SetUint(localCommonConverter.Uint64(from))
   117  		}
   118  	case "string":
   119  		convertFunc = func(from any, to reflect.Value) {
   120  			to.SetString(localCommonConverter.String(from))
   121  		}
   122  	case "float32":
   123  		convertFunc = func(from any, to reflect.Value) {
   124  			to.SetFloat(float64(localCommonConverter.Float32(from)))
   125  		}
   126  	case "float64":
   127  		convertFunc = func(from any, to reflect.Value) {
   128  			to.SetFloat(localCommonConverter.Float64(from))
   129  		}
   130  	case "Time", "time.Time":
   131  		convertFunc = func(from any, to reflect.Value) {
   132  			*to.Addr().Interface().(*time.Time) = localCommonConverter.Time(from)
   133  		}
   134  	case "GTime", "gtime.Time":
   135  		convertFunc = func(from any, to reflect.Value) {
   136  			v := localCommonConverter.GTime(from)
   137  			if v == nil {
   138  				v = gtime.New()
   139  			}
   140  			*to.Addr().Interface().(*gtime.Time) = *v
   141  		}
   142  	case "bool":
   143  		convertFunc = func(from any, to reflect.Value) {
   144  			to.SetBool(localCommonConverter.Bool(from))
   145  		}
   146  	case "[]byte":
   147  		convertFunc = func(from any, to reflect.Value) {
   148  			to.SetBytes(localCommonConverter.Bytes(from))
   149  		}
   150  	default:
   151  		return nil
   152  	}
   153  	return convertFunc
   154  }
   155  
   156  func (csi *CachedStructInfo) genPriorityTagAndFieldName(
   157  	field reflect.StructField, priorityTags []string,
   158  ) (priorityTagAndFieldName []string) {
   159  	for _, tag := range priorityTags {
   160  		value, ok := field.Tag.Lookup(tag)
   161  		if ok {
   162  			// If there's something else in the tag string,
   163  			// it uses the first part which is split using char ','.
   164  			// Example:
   165  			// orm:"id, priority"
   166  			// orm:"name, with:uid=id"
   167  			tagValueItems := strings.Split(value, ",")
   168  			// json:",omitempty"
   169  			trimmedTagName := strings.TrimSpace(tagValueItems[0])
   170  			if trimmedTagName != "" {
   171  				priorityTagAndFieldName = append(priorityTagAndFieldName, trimmedTagName)
   172  				break
   173  			}
   174  		}
   175  	}
   176  	priorityTagAndFieldName = append(priorityTagAndFieldName, field.Name)
   177  	return
   178  }
   179  
   180  func (csi *CachedStructInfo) checkTypeHasCustomConvert(fieldType reflect.Type) bool {
   181  	if fieldType.Kind() == reflect.Ptr {
   182  		fieldType = fieldType.Elem()
   183  	}
   184  	_, ok := customConvertTypeMap[fieldType]
   185  	return ok
   186  }
   187  
   188  func (csi *CachedStructInfo) genPtrConvertFunc(
   189  	convertFunc func(from any, to reflect.Value),
   190  ) func(from any, to reflect.Value) {
   191  	return func(from any, to reflect.Value) {
   192  		if to.IsNil() {
   193  			to.Set(reflect.New(to.Type().Elem()))
   194  		}
   195  		convertFunc(from, to.Elem())
   196  	}
   197  }