Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: internal variable naming for struct converting of package gconv #1

Merged
merged 4 commits into from
Apr 22, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 93 additions & 89 deletions util/gconv/gconv_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func doStruct(
// return v.UnmarshalValue(params)
// }
// Note that it's `pointerElemReflectValue` here not `pointerReflectValue`.
if ok, err := bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
return err
}
// Retrieve its element, may be struct at last.
Expand All @@ -166,21 +166,20 @@ func doStruct(
return nil
}

type fieldInfo struct {
// tag can be one of the following values
// 1. The key of paramKeyToAttrMap
// 2. It can be the specified tag attribute
// 3. The name of the elemFieldName
tag string
val any
index int
// Holds the info for subsequent converting.
type toBeConvertedFieldInfo struct {
Value any // Found value by tag name or field name from input.
FieldIndex int // The associated reflection field index.
FieldOrTagName string // Field name or tag name for field tag by priority tags.
}

var (
// Used to save some attributes related to subsequent operations
// key=elemFieldName
fieldInfoMap = map[string]fieldInfo{}
priorityTagArray []string
priorityTagArray []string
elemFieldName string
elemFieldType reflect.StructField
elemFieldValue reflect.Value
elemType = pointerElemReflectValue.Type()
toBeConvertedFieldNameToInfoMap = map[string]toBeConvertedFieldInfo{} // key=elemFieldName
)

if priorityTag != "" {
Expand All @@ -189,33 +188,6 @@ func doStruct(
priorityTagArray = gtag.StructTagPriority
}

var getTag = func(field reflect.StructField, priorityTags []string) string {
for _, tag := range priorityTags {
value, ok := field.Tag.Lookup(tag)
if ok {
// If there's something else in the tag string,
// it uses the first part which is split using char ','.
// Eg:
// orm:"id, priority"
// orm:"name, with:uid=id"
split := strings.Split(value, ",")
// json:",omitempty"
trimTag := strings.TrimSpace(split[0])
return trimTag
}
}
return ""
}

// The key of the attrMap is the attribute name of the struct,
// and the value is its replaced name for later comparison to improve performance.
var (
elemFieldName string
elemFieldType reflect.StructField
elemFieldValue reflect.Value
elemType = pointerElemReflectValue.Type()
)

for i := 0; i < pointerElemReflectValue.NumField(); i++ {
elemFieldType = elemType.Field(i)
elemFieldName = elemFieldType.Name
Expand All @@ -224,22 +196,24 @@ func doStruct(
continue
}

fieldTag := getTag(elemFieldType, priorityTagArray)
var fieldTagName = getTagNameFromField(elemFieldType, priorityTagArray)
// Maybe it's struct/*struct embedded.
if elemFieldType.Anonymous {
// type Name struct {
// LastName string `json:"lastName"`
// LastName string `json:"lastName"`
// FirstName string `json:"firstName"`
// }
//
// type User struct {
// Name `json:"name"`
// Other fields...
// // ...
// }
//
// It is only recorded if the name has a fieldTag
if fieldTag != "" {
fieldInfoMap[elemFieldName] = fieldInfo{
index: elemFieldType.Index[0],
tag: fieldTag,
if fieldTagName != "" {
toBeConvertedFieldNameToInfoMap[elemFieldName] = toBeConvertedFieldInfo{
FieldIndex: elemFieldType.Index[0],
FieldOrTagName: fieldTagName,
}
}

Expand All @@ -256,81 +230,111 @@ func doStruct(
}
} else {
// Use the native elemFieldName name as the fieldTag
if fieldTag == "" {
fieldTag = elemFieldName
if fieldTagName == "" {
fieldTagName = elemFieldName
}
fieldInfoMap[elemFieldName] = fieldInfo{
index: elemFieldType.Index[0],
tag: fieldTag,
toBeConvertedFieldNameToInfoMap[elemFieldName] = toBeConvertedFieldInfo{
FieldIndex: elemFieldType.Index[0],
FieldOrTagName: fieldTagName,
}
}
}

if len(fieldInfoMap) == 0 {
// Nothing to be converted.
if len(toBeConvertedFieldNameToInfoMap) == 0 {
return nil
}

for fieldName, field := range fieldInfoMap {
paramsVal, ok := paramsMap[field.tag]
if ok {
field.val = paramsVal
fieldInfoMap[fieldName] = field
// Search the parameter value for the field.
var paramsValue any
for fieldName, fieldInfo := range toBeConvertedFieldNameToInfoMap {
if paramsValue, ok = paramsMap[fieldInfo.FieldOrTagName]; ok {
fieldInfo.Value = paramsValue
toBeConvertedFieldNameToInfoMap[fieldName] = fieldInfo
}
}

// First search according to custom rules. If a direct assignment is found, reduce the number of subsequent map searches.
// Firstly, search according to custom mapping rules.
// If a possible direct assignment is found, reduce the number of subsequent map searches.
var fieldInfo toBeConvertedFieldInfo
for paramKey, fieldName := range paramKeyToAttrMap {
// Prevent setting of non-existent fields
field, ok := fieldInfoMap[fieldName]
fieldInfo, ok = toBeConvertedFieldNameToInfoMap[fieldName]
if ok {
// Prevent non-existent values from being set
paramsVal, ok := paramsMap[paramKey]
if ok {
field.val = paramsVal
fieldInfoMap[fieldName] = field
// Prevent non-existent values from being set.
if paramsValue, ok = paramsMap[paramKey]; ok {
fieldInfo.Value = paramsValue
toBeConvertedFieldNameToInfoMap[fieldName] = fieldInfo
}
}
}

// Indicates that those values have been used and cannot be reused.
usedParamsKey := map[string]struct{}{}

for fieldName, field := range fieldInfoMap {
var (
paramKey string
paramValue any
fieldName string
// Indicates that those values have been used and cannot be reused.
usedParamsKeyOrTagNameMap = map[string]struct{}{}
)
for fieldName, fieldInfo = range toBeConvertedFieldNameToInfoMap {
// If it is not empty, the tag or elemFieldName name matches
if field.val != nil {
if err := bindVarToStructAttrWithFieldIndex(
pointerElemReflectValue, fieldName, field.index, field.val, paramKeyToAttrMap,
if fieldInfo.Value != nil {
if err = bindVarToStructAttrWithFieldIndex(
pointerElemReflectValue, fieldName, fieldInfo.FieldIndex, fieldInfo.Value, paramKeyToAttrMap,
); err != nil {
return err
}
// It is necessary to delete the set fields for quick traversal later.
usedParamsKey[field.tag] = struct{}{}
} else {
// If it is empty, a fuzzy match is required
key, val := fuzzyMatchingFieldName(fieldName, paramsMap, usedParamsKey)
if val != nil {
if err := bindVarToStructAttrWithFieldIndex(
pointerElemReflectValue, fieldName, field.index, val, paramKeyToAttrMap,
); err != nil {
return err
}
// It is necessary to delete the set fields for quick traversal later.
usedParamsKey[key] = struct{}{}
usedParamsKeyOrTagNameMap[fieldInfo.FieldOrTagName] = struct{}{}
continue
}

// If value is nil, a fuzzy match is used for search the key and value for converting.
paramKey, paramValue = fuzzyMatchingFieldName(fieldName, paramsMap, usedParamsKeyOrTagNameMap)
if paramValue != nil {
if err = bindVarToStructAttrWithFieldIndex(
pointerElemReflectValue, fieldName, fieldInfo.FieldIndex, paramValue, paramKeyToAttrMap,
); err != nil {
return err
}
usedParamsKeyOrTagNameMap[paramKey] = struct{}{}
}
}
return nil
}

func fuzzyMatchingFieldName(fieldName string, paramsMap map[string]any, usedParamsKey map[string]struct{}) (string, any) {
func getTagNameFromField(field reflect.StructField, priorityTags []string) string {
for _, tag := range priorityTags {
value, ok := field.Tag.Lookup(tag)
if ok {
// If there's something else in the tag string,
// it uses the first part which is split using char ','.
// Example:
// orm:"id, priority"
// orm:"name, with:uid=id"
array := strings.Split(value, ",")
// json:",omitempty"
trimmedTagName := strings.TrimSpace(array[0])
return trimmedTagName
}
}
return ""
}

// fuzzy matching rule:
// to match field name and param key in case-insensitive and without symbols.
func fuzzyMatchingFieldName(
fieldName string,
paramsMap map[string]any,
usedParamsKeyMap map[string]struct{},
) (string, any) {
fieldName = utils.RemoveSymbols(fieldName)
for paramKey, paramVal := range paramsMap {
if _, ok := usedParamsKey[paramKey]; ok {
for paramKey, paramValue := range paramsMap {
if _, ok := usedParamsKeyMap[paramKey]; ok {
continue
}
removeParamKeyUnderline := utils.RemoveSymbols(paramKey)
if strings.EqualFold(fieldName, removeParamKeyUnderline) {
return paramKey, paramVal
return paramKey, paramValue
}
}
return "", nil
Expand Down