gf/util/gconv/gconv_struct.go

576 lines
18 KiB
Go
Raw Normal View History

2021-01-17 21:46:25 +08:00
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gconv
import (
2021-11-13 23:30:31 +08:00
"reflect"
"strings"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gstructs"
)
// Struct maps the params key-value pairs to the corresponding struct object's attributes.
// The third parameter `mapping` is unnecessary, indicating the mapping rules between the
// custom key name and the attribute name(case-sensitive).
2019-05-08 21:03:04 +08:00
//
// Note:
// 1. The `params` can be any type of map/struct, usually a map.
// 2. The `pointer` should be type of *struct/**struct, which is a pointer to struct object
// or struct pointer.
// 3. Only the public attributes of struct object can be mapped.
// 4. If `params` is a map, the key of the map `params` can be lowercase.
2019-05-08 21:03:04 +08:00
// It will automatically convert the first letter of the key to uppercase
// in mapping procedure to do the matching.
// It ignores the map key, if it does not match.
func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return Scan(params, pointer, mapping...)
}
// StructTag acts as Struct but also with support for priority tag feature, which retrieves the
// specified tags for `params` key-value items to struct attribute names mapping.
// The parameter `priorityTag` supports multiple tags that can be joined with char ','.
func StructTag(params interface{}, pointer interface{}, priorityTag string) (err error) {
return doStruct(params, pointer, nil, priorityTag)
}
2021-09-15 21:17:45 +08:00
// doStructWithJsonCheck checks if given `params` is JSON, it then uses json.Unmarshal doing the converting.
func doStructWithJsonCheck(params interface{}, pointer interface{}) (err error, ok bool) {
switch r := params.(type) {
case []byte:
if json.Valid(r) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
2021-09-15 21:46:58 +08:00
if rv.IsNil() {
return nil, false
}
2021-09-15 21:17:45 +08:00
return json.UnmarshalUseNumber(r, rv.Interface()), true
2021-06-10 20:17:53 +08:00
} else if rv.CanAddr() {
2021-09-15 21:17:45 +08:00
return json.UnmarshalUseNumber(r, rv.Addr().Interface()), true
}
} else {
2021-09-15 21:17:45 +08:00
return json.UnmarshalUseNumber(r, pointer), true
}
}
case string:
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
2021-09-15 21:46:58 +08:00
if rv.IsNil() {
return nil, false
}
2021-09-15 21:17:45 +08:00
return json.UnmarshalUseNumber(paramsBytes, rv.Interface()), true
2021-06-10 20:17:53 +08:00
} else if rv.CanAddr() {
2021-09-15 21:17:45 +08:00
return json.UnmarshalUseNumber(paramsBytes, rv.Addr().Interface()), true
}
} else {
2021-09-15 21:17:45 +08:00
return json.UnmarshalUseNumber(paramsBytes, pointer), true
}
}
default:
// The `params` might be struct that implements interface function Interface, eg: gvar.Var.
if v, ok := params.(iInterface); ok {
2021-09-15 21:17:45 +08:00
return doStructWithJsonCheck(v.Interface(), pointer)
}
}
return nil, false
}
// doStruct is the core internal converting function for any data to struct.
func doStruct(params interface{}, pointer interface{}, mapping map[string]string, priorityTag string) (err error) {
if params == nil {
// If `params` is nil, no conversion.
return nil
}
if pointer == nil {
return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil")
}
defer func() {
// Catch the panic, especially the reflection operation panics.
if exception := recover(); exception != nil {
if v, ok := exception.(error); ok && gerror.HasStack(v) {
err = v
2021-09-15 21:17:45 +08:00
} else {
err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%+v", exception)
}
}
2021-09-15 21:17:45 +08:00
}()
// JSON content converting.
err, ok := doStructWithJsonCheck(params, pointer)
if err != nil {
return err
}
if ok {
return nil
}
2020-12-04 18:17:11 +08:00
var (
paramsReflectValue reflect.Value
2021-09-15 21:17:45 +08:00
paramsInterface interface{} // DO NOT use `params` directly as it might be type `reflect.Value`
2020-12-04 18:17:11 +08:00
pointerReflectValue reflect.Value
pointerReflectKind reflect.Kind
pointerElemReflectValue reflect.Value // The pointed element.
)
if v, ok := params.(reflect.Value); ok {
paramsReflectValue = v
} else {
paramsReflectValue = reflect.ValueOf(params)
}
paramsInterface = paramsReflectValue.Interface()
2020-12-04 18:17:11 +08:00
if v, ok := pointer.(reflect.Value); ok {
pointerReflectValue = v
pointerElemReflectValue = v
} else {
pointerReflectValue = reflect.ValueOf(pointer)
pointerReflectKind = pointerReflectValue.Kind()
if pointerReflectKind != reflect.Ptr {
return gerror.NewCodef(gcode.CodeInvalidParameter, "object pointer should be type of '*struct', but got '%v'", pointerReflectKind)
2019-06-19 09:06:52 +08:00
}
// Using IsNil on reflect.Ptr variable is OK.
2020-12-04 18:17:11 +08:00
if !pointerReflectValue.IsValid() || pointerReflectValue.IsNil() {
return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil")
2019-06-19 09:06:52 +08:00
}
2020-12-04 18:17:11 +08:00
pointerElemReflectValue = pointerReflectValue.Elem()
}
2021-09-15 21:17:45 +08:00
2020-12-04 18:17:11 +08:00
// If `params` and `pointer` are the same type, the do directly assignment.
// For performance enhancement purpose.
if pointerElemReflectValue.IsValid() && pointerElemReflectValue.Type() == paramsReflectValue.Type() {
pointerElemReflectValue.Set(paramsReflectValue)
return nil
}
2021-01-17 20:56:38 +08:00
// Normal unmarshalling interfaces checks.
if err, ok = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok {
2021-01-17 20:56:38 +08:00
return err
}
// It automatically creates struct object if necessary.
// For example, if `pointer` is **User, then `elem` is *User, which is a pointer to User.
2020-12-04 18:17:11 +08:00
if pointerElemReflectValue.Kind() == reflect.Ptr {
if !pointerElemReflectValue.IsValid() || pointerElemReflectValue.IsNil() {
e := reflect.New(pointerElemReflectValue.Type().Elem()).Elem()
pointerElemReflectValue.Set(e.Addr())
}
2021-11-13 23:30:31 +08:00
// if v, ok := pointerElemReflectValue.Interface().(iUnmarshalValue); ok {
2021-01-17 20:56:38 +08:00
// return v.UnmarshalValue(params)
2021-11-13 23:30:31 +08:00
// }
2021-01-17 20:56:38 +08:00
// Note that it's `pointerElemReflectValue` here not `pointerReflectValue`.
if err, ok = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
2021-01-17 20:56:38 +08:00
return err
}
2020-12-04 18:17:11 +08:00
// Retrieve its element, may be struct at last.
pointerElemReflectValue = pointerElemReflectValue.Elem()
}
// paramsMap is the map[string]interface{} type variable for params.
// DO NOT use MapDeep here.
paramsMap := Map(paramsInterface)
2020-12-04 18:17:11 +08:00
if paramsMap == nil {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
2021-12-17 17:28:02 +08:00
`convert params from "%#v" to "map[string]interface{}" failed`,
params,
)
}
2019-06-19 09:06:52 +08:00
// It only performs one converting to the same attribute.
// doneMap is used to check repeated converting, its key is the real attribute name
// of the struct.
doneMap := make(map[string]struct{})
// 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 (
2022-04-11 21:54:23 +08:00
tempName string
elemFieldType reflect.StructField
elemFieldValue reflect.Value
elemType = pointerElemReflectValue.Type()
attrToCheckNameMap = make(map[string]string)
)
2020-12-04 18:17:11 +08:00
for i := 0; i < pointerElemReflectValue.NumField(); i++ {
elemFieldType = elemType.Field(i)
2019-06-19 09:06:52 +08:00
// Only do converting to public attributes.
if !utils.IsLetterUpper(elemFieldType.Name[0]) {
2019-06-19 09:06:52 +08:00
continue
}
// Maybe it's struct/*struct embedded.
if elemFieldType.Anonymous {
elemFieldValue = pointerElemReflectValue.Field(i)
// Ignore the interface attribute if it's nil.
if elemFieldValue.Kind() == reflect.Interface {
elemFieldValue = elemFieldValue.Elem()
if !elemFieldValue.IsValid() {
continue
}
}
if err = doStruct(paramsMap, elemFieldValue, mapping, priorityTag); err != nil {
return err
}
} else {
tempName = elemFieldType.Name
2022-04-11 21:54:23 +08:00
attrToCheckNameMap[tempName] = utils.RemoveSymbols(tempName)
}
2019-06-19 09:06:52 +08:00
}
2022-04-11 21:54:23 +08:00
if len(attrToCheckNameMap) == 0 {
2019-10-26 10:58:07 +08:00
return nil
}
// The key of the tagMap is the attribute name of the struct,
// and the value is its replaced tag name for later comparison to improve performance.
var (
2022-04-11 21:54:23 +08:00
attrToTagCheckNameMap = make(map[string]string)
priorityTagArray []string
)
if priorityTag != "" {
priorityTagArray = append(utils.SplitAndTrim(priorityTag, ","), StructTagPriority...)
} else {
priorityTagArray = StructTagPriority
}
2022-04-11 21:54:23 +08:00
tagToAttrNameMap, err := gstructs.TagMapName(pointerElemReflectValue, priorityTagArray)
if err != nil {
return err
}
2022-04-11 21:54:23 +08:00
for tagName, attributeName := range tagToAttrNameMap {
// 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"
2022-04-11 21:54:23 +08:00
attrToTagCheckNameMap[attributeName] = utils.RemoveSymbols(strings.Split(tagName, ",")[0])
2022-02-15 00:21:05 +08:00
// If tag and attribute values both exist in `paramsMap`,
// it then uses the tag value overwriting the attribute value in `paramsMap`.
2022-04-08 18:11:17 +08:00
if paramsMap[tagName] != nil && paramsMap[attributeName] != nil {
paramsMap[attributeName] = paramsMap[tagName]
2022-02-15 00:21:05 +08:00
}
}
var (
attrName string
checkName string
)
2022-04-11 21:54:23 +08:00
for paramName, paramValue := range paramsMap {
attrName = ""
// It firstly checks the passed mapping rules.
if len(mapping) > 0 {
2022-04-11 21:54:23 +08:00
if passedAttrKey, ok := mapping[paramName]; ok {
attrName = passedAttrKey
}
}
// It secondly checks the predefined tags and matching rules.
if attrName == "" {
2022-04-11 21:54:23 +08:00
// It firstly considers `paramName` as accurate tag name,
// and retrieve attribute name from `tagToAttrNameMap` .
attrName = tagToAttrNameMap[paramName]
2022-04-08 18:11:17 +08:00
if attrName == "" {
2022-04-11 21:54:23 +08:00
checkName = utils.RemoveSymbols(paramName)
2022-04-08 18:11:17 +08:00
// Loop to find the matched attribute name with or without
// string cases and chars like '-'/'_'/'.'/' '.
2022-04-08 18:11:17 +08:00
// Matching the parameters to struct tag names.
// The `attrKey` is the attribute name of the struct.
2022-04-11 21:54:23 +08:00
for attrKey, cmpKey := range attrToTagCheckNameMap {
2022-04-08 18:11:17 +08:00
if strings.EqualFold(checkName, cmpKey) {
attrName = attrKey
break
}
}
2019-06-19 09:06:52 +08:00
}
2022-04-08 18:11:17 +08:00
// Matching the parameters to struct attributes.
if attrName == "" {
2022-04-11 21:54:23 +08:00
for attrKey, cmpKey := range attrToCheckNameMap {
// Eg:
// UserName eq user_name
// User-Name eq username
// username eq userName
// etc.
if strings.EqualFold(checkName, cmpKey) {
attrName = attrKey
break
}
}
}
}
// No matching, it gives up this attribute converting.
if attrName == "" {
2019-06-19 09:06:52 +08:00
continue
}
// If the attribute name is already checked converting, then skip it.
2022-02-15 00:21:05 +08:00
if _, ok = doneMap[attrName]; ok {
continue
}
// Mark it done.
doneMap[attrName] = struct{}{}
2022-04-11 21:54:23 +08:00
if err = bindVarToStructAttr(pointerElemReflectValue, attrName, paramValue, mapping); err != nil {
2019-06-19 09:06:52 +08:00
return err
}
}
return nil
}
// bindVarToStructAttr sets value to struct object attribute by name.
2022-04-12 12:09:09 +08:00
func bindVarToStructAttr(structReflectValue reflect.Value, attrName string, value interface{}, mapping map[string]string) (err error) {
structFieldValue := structReflectValue.FieldByName(attrName)
2019-06-19 09:06:52 +08:00
if !structFieldValue.IsValid() {
return nil
}
// CanSet checks whether attribute is public accessible.
2019-06-19 09:06:52 +08:00
if !structFieldValue.CanSet() {
return nil
}
defer func() {
if exception := recover(); exception != nil {
2021-09-15 21:17:45 +08:00
if err = bindVarToReflectValue(structFieldValue, value, mapping); err != nil {
2022-04-12 12:09:09 +08:00
err = gerror.Wrapf(err, `error binding value to attribute "%s"`, attrName)
}
2019-06-19 09:06:52 +08:00
}
}()
// Directly converting.
if empty.IsNil(value) {
structFieldValue.Set(reflect.Zero(structFieldValue.Type()))
} else {
2022-02-17 22:02:51 +08:00
// Common interface check.
var ok bool
if err, ok = bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
return err
}
// Default converting.
2022-04-12 12:09:09 +08:00
doConvertWithReflectValueSet(structFieldValue, doConvertInput{
FromValue: value,
ToTypeName: structFieldValue.Type().String(),
ReferValue: structFieldValue,
})
}
2019-06-19 09:06:52 +08:00
return nil
}
2021-09-15 21:17:45 +08:00
// bindVarToReflectValueWithInterfaceCheck does bind using common interfaces checks.
2021-01-17 20:56:38 +08:00
func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value interface{}) (err error, ok bool) {
var pointer interface{}
if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() {
2021-01-17 22:06:07 +08:00
reflectValueAddr := reflectValue.Addr()
if reflectValueAddr.IsNil() || !reflectValueAddr.IsValid() {
return nil, false
}
2021-01-17 20:56:38 +08:00
// Not a pointer, but can token address, that makes it can be unmarshalled.
pointer = reflectValue.Addr().Interface()
} else {
2021-01-17 22:06:07 +08:00
if reflectValue.IsNil() || !reflectValue.IsValid() {
return nil, false
}
2021-01-17 20:56:38 +08:00
pointer = reflectValue.Interface()
}
// UnmarshalValue.
if v, ok := pointer.(iUnmarshalValue); ok {
2021-01-17 20:56:38 +08:00
return v.UnmarshalValue(value), ok
}
// UnmarshalText.
if v, ok := pointer.(iUnmarshalText); ok {
var valueBytes []byte
2021-01-17 20:56:38 +08:00
if b, ok := value.([]byte); ok {
valueBytes = b
} else if s, ok := value.(string); ok {
valueBytes = []byte(s)
}
if len(valueBytes) > 0 {
return v.UnmarshalText(valueBytes), ok
}
}
// UnmarshalJSON.
if v, ok := pointer.(iUnmarshalJSON); ok {
var valueBytes []byte
if b, ok := value.([]byte); ok {
valueBytes = b
} else if s, ok := value.(string); ok {
valueBytes = []byte(s)
}
if len(valueBytes) > 0 {
// If it is not a valid JSON string, it then adds char `"` on its both sides to make it is.
if !json.Valid(valueBytes) {
newValueBytes := make([]byte, len(valueBytes)+2)
newValueBytes[0] = '"'
newValueBytes[len(newValueBytes)-1] = '"'
copy(newValueBytes[1:], valueBytes)
valueBytes = newValueBytes
}
return v.UnmarshalJSON(valueBytes), ok
}
}
if v, ok := pointer.(iSet); ok {
2021-01-17 20:56:38 +08:00
v.Set(value)
return nil, ok
}
return nil, false
}
// bindVarToReflectValue sets `value` to reflect value object `structFieldValue`.
2021-09-15 21:17:45 +08:00
func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, mapping map[string]string) (err error) {
// JSON content converting.
err, ok := doStructWithJsonCheck(value, structFieldValue)
if err != nil {
return err
}
if ok {
return nil
}
2020-06-16 20:18:40 +08:00
kind := structFieldValue.Kind()
2022-04-12 12:09:09 +08:00
// Converting using `Set` interface implements, for some types.
2020-06-16 20:18:40 +08:00
switch kind {
case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Interface:
if !structFieldValue.IsNil() {
if v, ok := structFieldValue.Interface().(iSet); ok {
2020-06-16 20:18:40 +08:00
v.Set(value)
return nil
2020-06-16 19:30:35 +08:00
}
2019-06-19 09:06:52 +08:00
}
}
2020-06-16 19:30:35 +08:00
2022-04-12 12:09:09 +08:00
// Converting using reflection by kind.
2020-06-16 20:18:40 +08:00
switch kind {
case reflect.Map:
return doMapToMap(value, structFieldValue, mapping)
2019-06-19 09:06:52 +08:00
case reflect.Struct:
// Recursively converting for struct attribute.
2021-09-15 21:17:45 +08:00
if err = doStruct(value, structFieldValue, nil, ""); err != nil {
// Note there's reflect conversion mechanism here.
structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type()))
2019-06-19 09:06:52 +08:00
}
2020-01-20 21:25:55 +08:00
// Note that the slice element might be type of struct,
// so it uses Struct function doing the converting internally.
case reflect.Slice, reflect.Array:
2022-02-22 20:19:41 +08:00
var (
reflectArray reflect.Value
reflectValue = reflect.ValueOf(value)
)
if reflectValue.Kind() == reflect.Slice || reflectValue.Kind() == reflect.Array {
reflectArray = reflect.MakeSlice(structFieldValue.Type(), reflectValue.Len(), reflectValue.Len())
if reflectValue.Len() > 0 {
var (
elemType = reflectArray.Index(0).Type()
elemTypeName string
converted bool
)
for i := 0; i < reflectValue.Len(); i++ {
converted = false
elemTypeName = elemType.Name()
if elemTypeName == "" {
elemTypeName = elemType.String()
}
var elem reflect.Value
if elemType.Kind() == reflect.Ptr {
elem = reflect.New(elemType.Elem()).Elem()
2019-06-19 09:06:52 +08:00
} else {
2022-02-22 20:19:41 +08:00
elem = reflect.New(elemType).Elem()
}
if elem.Kind() == reflect.Struct {
if err = doStruct(reflectValue.Index(i).Interface(), elem, nil, ""); err == nil {
converted = true
2019-06-19 09:06:52 +08:00
}
}
2022-02-22 20:19:41 +08:00
if !converted {
2022-04-12 12:09:09 +08:00
doConvertWithReflectValueSet(elem, doConvertInput{
2022-02-22 20:19:41 +08:00
FromValue: reflectValue.Index(i).Interface(),
ToTypeName: elemTypeName,
ReferValue: elem,
2022-04-12 12:09:09 +08:00
})
2022-02-22 20:19:41 +08:00
}
if elemType.Kind() == reflect.Ptr {
// Before it sets the `elem` to array, do pointer converting if necessary.
elem = elem.Addr()
}
reflectArray.Index(i).Set(elem)
2019-06-19 09:06:52 +08:00
}
}
} else {
2022-02-22 20:19:41 +08:00
reflectArray = reflect.MakeSlice(structFieldValue.Type(), 1, 1)
var (
elem reflect.Value
elemType = reflectArray.Index(0).Type()
elemTypeName = elemType.Name()
converted bool
)
if elemTypeName == "" {
elemTypeName = elemType.String()
}
if elemType.Kind() == reflect.Ptr {
elem = reflect.New(elemType.Elem()).Elem()
2019-06-19 09:06:52 +08:00
} else {
2022-02-22 20:19:41 +08:00
elem = reflect.New(elemType).Elem()
}
if elem.Kind() == reflect.Struct {
if err = doStruct(value, elem, nil, ""); err == nil {
converted = true
2019-06-19 09:06:52 +08:00
}
}
2022-02-22 20:19:41 +08:00
if !converted {
2022-04-12 12:09:09 +08:00
doConvertWithReflectValueSet(elem, doConvertInput{
2022-02-22 20:19:41 +08:00
FromValue: value,
ToTypeName: elemTypeName,
ReferValue: elem,
2022-04-12 12:09:09 +08:00
})
2022-02-22 20:19:41 +08:00
}
if elemType.Kind() == reflect.Ptr {
// Before it sets the `elem` to array, do pointer converting if necessary.
elem = elem.Addr()
}
reflectArray.Index(0).Set(elem)
2019-06-19 09:06:52 +08:00
}
2022-02-22 20:19:41 +08:00
structFieldValue.Set(reflectArray)
2019-06-19 09:06:52 +08:00
case reflect.Ptr:
item := reflect.New(structFieldValue.Type().Elem())
2022-02-17 11:46:35 +08:00
if err, ok = bindVarToReflectValueWithInterfaceCheck(item, value); ok {
structFieldValue.Set(item)
return err
}
elem := item.Elem()
2021-09-15 21:17:45 +08:00
if err = bindVarToReflectValue(elem, value, mapping); err == nil {
structFieldValue.Set(elem.Addr())
2019-06-19 09:06:52 +08:00
}
// It mainly and specially handles the interface of nil value.
case reflect.Interface:
if value == nil {
// Specially.
structFieldValue.Set(reflect.ValueOf((*interface{})(nil)))
} else {
// Note there's reflect conversion mechanism here.
structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type()))
}
2019-06-19 09:06:52 +08:00
default:
defer func() {
if exception := recover(); exception != nil {
2021-07-20 23:02:02 +08:00
err = gerror.NewCodef(
gcode.CodeInternalError,
2021-07-20 23:02:02 +08:00
`cannot convert value "%+v" to type "%s":%+v`,
value,
structFieldValue.Type().String(),
exception,
)
}
}()
// It here uses reflect converting `value` to type of the attribute and assigns
// the result value to the attribute. It might fail and panic if the usual Go
// conversion rules do not allow conversion.
structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type()))
2019-06-19 09:06:52 +08:00
}
return nil
}