gf/g/util/gconv/gconv_struct.go

256 lines
9.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 (
"errors"
"fmt"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/third/github.com/fatih/structs"
"reflect"
"strings"
)
// 将params键值对参数映射到对应的struct对象属性上第三个参数mapping为非必需表示自定义名称与属性名称的映射关系。
// 需要注意:
// 1、第二个参数应当为struct对象指针
// 2、struct对象的**公开属性(首字母大写)**才能被映射赋值;
// 3、map中的键名可以为小写映射转换时会自动将键名首字母转为大写做匹配映射如果无法匹配则忽略
func Struct(params interface{}, objPointer interface{}, attrMapping...map[string]string) error {
if params == nil {
return nil
}
isParamMap := true
paramsMap := (map[string]interface{})(nil)
// 先将参数转为 map[string]interface{} 类型
if m, ok := params.(map[string]interface{}); ok {
paramsMap = m
} else {
paramsMap = make(map[string]interface{})
if reflect.ValueOf(params).Kind() == reflect.Map {
ks := reflect.ValueOf(params).MapKeys()
vs := reflect.ValueOf(params)
for _, k := range ks {
paramsMap[String(k.Interface())] = vs.MapIndex(k).Interface()
}
} else {
isParamMap = false
}
}
// struct的反射对象
elem := reflect.Value{}
if v, ok := objPointer.(reflect.Value); ok {
elem = v
} else {
elem = reflect.ValueOf(objPointer).Elem()
}
// 如果给定的参数不是map类型那么直接将参数值映射到第一个属性上
if !isParamMap {
if err := bindVarToStructByIndex(elem, 0, params); err != nil {
return err
}
return nil
}
// 已执行过转换的属性,只执行一次转换
dmap := make(map[string]bool)
// 首先按照传递的映射关系进行匹配
if len(attrMapping) > 0 && len(attrMapping[0]) > 0 {
for mappingk, mappingv := range attrMapping[0] {
if v, ok := paramsMap[mappingk]; ok {
dmap[mappingv] = true
if err := bindVarToStructAttr(elem, mappingv, v); err != nil {
return err
}
}
}
}
// 其次匹配对象定义时绑定的属性名称
// 标签映射关系map如果有的话
tagmap := getTagMapOfStruct(objPointer)
for tagk, tagv := range tagmap {
if _, ok := dmap[tagv]; ok {
continue
}
if v, ok := paramsMap[tagk]; ok {
dmap[tagv] = true
if err := bindVarToStructAttr(elem, tagv, v); err != nil {
return err
}
}
}
// 最后按照默认规则进行匹配
attrMap := make(map[string]struct{})
elemType := elem.Type()
for i := 0; i < elem.NumField(); i++ {
attrMap[elemType.Field(i).Name] = struct{}{}
}
for mapk, mapv := range paramsMap {
name := ""
for _, checkName := range []string {
gstr.UcFirst(mapk),
gstr.ReplaceByMap(mapk, map[string]string{
"_" : "",
"-" : "",
" " : "",
})} {
if _, ok := dmap[checkName]; ok {
continue
}
if _, ok := tagmap[checkName]; ok {
continue
}
// 循环查找属性名称进行匹配
for value, _ := range attrMap {
if strings.EqualFold(checkName, value) {
name = value
break
}
if strings.EqualFold(checkName, gstr.Replace(value, "_", "")) {
name = value
break
}
}
if name != "" {
break
}
}
// 如果没有匹配到属性名称,放弃
if name == "" {
continue
}
if err := bindVarToStructAttr(elem, name, mapv); err != nil {
return err
}
}
return nil
}
// 解析指针对象的tag
func getTagMapOfStruct(objPointer interface{}) map[string]string {
tagmap := make(map[string]string)
// 反射类型判断
fields := ([]*structs.Field)(nil)
if v, ok := objPointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())
} else {
fields = structs.Fields(objPointer)
}
// 将struct中定义的属性转换名称构建成tagmap
for _, field := range fields {
tag := field.Tag("gconv")
if tag == "" {
tag = field.Tag("json")
}
if tag != "" {
for _, v := range strings.Split(tag, ",") {
tagmap[strings.TrimSpace(v)] = field.Name()
}
}
}
return tagmap
}
// 将参数值绑定到对象指定名称的属性上
func bindVarToStructAttr(elem reflect.Value, name string, value interface{}) (err error) {
structFieldValue := elem.FieldByName(name)
// 键名与对象属性匹配检测map中如果有struct不存在的属性那么不做处理直接return
if !structFieldValue.IsValid() {
return nil
}
// CanSet的属性必须为公开属性(首字母大写)
if !structFieldValue.CanSet() {
return nil
}
// 必须将value转换为struct属性的数据类型这里必须用到gconv包
defer func() {
// 如果转换失败,那么可能是类型不匹配造成(例如属性包含自定义类型),那么执行递归转换
if recover() != nil {
err = bindVarToReflectValue(structFieldValue, value)
}
}()
structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))
return nil
}
// 将参数值绑定到对象指定索引位置的属性上
func bindVarToStructByIndex(elem reflect.Value, index int, value interface{}) (err error) {
structFieldValue := elem.FieldByIndex([]int{index})
// 键名与对象属性匹配检测
if !structFieldValue.IsValid() {
return nil
}
// CanSet的属性必须为公开属性(首字母大写)
if !structFieldValue.CanSet() {
return nil
}
// 必须将value转换为struct属性的数据类型这里必须用到gconv包
defer func() {
// 如果转换失败,那么可能是类型不匹配造成(例如属性包含自定义类型),那么执行递归转换
if recover() != nil {
err = bindVarToReflectValue(structFieldValue, value)
}
}()
structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))
return nil
}
// 当默认的基本类型转换失败时通过recover判断后执行反射类型转换(处理复杂类型)
func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) error {
switch structFieldValue.Kind() {
// 属性为结构体
case reflect.Struct:
Struct(value, structFieldValue)
// 属性为数组类型
case reflect.Slice: fallthrough
case reflect.Array:
a := reflect.Value{}
v := reflect.ValueOf(value)
if v.Kind() == reflect.Slice || v.Kind() == reflect.Array {
if v.Len() > 0 {
a = reflect.MakeSlice(structFieldValue.Type(), v.Len(), v.Len())
t := a.Index(0).Type()
for i := 0; i < v.Len(); i++ {
if t.Kind() == reflect.Ptr {
e := reflect.New(t.Elem()).Elem()
Struct(v.Index(i).Interface(), e)
a.Index(i).Set(e.Addr())
} else {
e := reflect.New(t).Elem()
Struct(v.Index(i).Interface(), e)
a.Index(i).Set(e)
}
}
}
} else {
a = reflect.MakeSlice(structFieldValue.Type(), 1, 1)
t := a.Index(0).Type()
if t.Kind() == reflect.Ptr {
e := reflect.New(t.Elem()).Elem()
Struct(value, e)
a.Index(0).Set(e.Addr())
} else {
e := reflect.New(t).Elem()
Struct(value, e)
a.Index(0).Set(e)
}
}
structFieldValue.Set(a)
// 属性为指针类型
case reflect.Ptr:
e := reflect.New(structFieldValue.Type().Elem()).Elem()
Struct(value, e)
structFieldValue.Set(e.Addr())
default:
return errors.New(fmt.Sprintf(`cannot convert to type "%s"`, structFieldValue.Type().String()))
}
return nil
}