Files
gf/g/util/gconv/gconv_struct.go

197 lines
7.4 KiB
Go
Raw Normal View History

// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package gconv
import (
"gitee.com/johng/gf/g/util/gstr"
"reflect"
"gitee.com/johng/gf/third/github.com/fatih/structs"
"strings"
2018-09-28 13:33:41 +08:00
"errors"
"fmt"
)
// 将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的反射对象
2018-09-30 13:31:03 +08:00
elem := reflect.Value{}
if v, ok := objPointer.(reflect.Value); ok {
elem = v
} else {
elem = reflect.ValueOf(objPointer).Elem()
}
// 如果给定的参数不是map类型那么直接将参数值映射到第一个属性上
if !isParamMap {
2018-09-28 13:33:41 +08:00
if err := bindVarToStructByIndex(elem, 0, params); err != nil {
return err
}
return nil
}
2018-09-30 13:31:03 +08:00
// 已执行过转换的属性,只执行一次转换
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
2018-09-28 13:33:41 +08:00
if err := bindVarToStruct(elem, mappingv, v); err != nil {
return err
}
}
}
}
// 其次匹配对象定义时绑定的属性名称
2018-09-30 13:31:03 +08:00
// 标签映射关系map如果有的话
tagmap := getTagMapOfStruct(objPointer)
for tagk, tagv := range tagmap {
if _, ok := dmap[tagv]; ok {
continue
}
if v, ok := paramsMap[tagk]; ok {
dmap[tagv] = true
2018-09-28 13:33:41 +08:00
if err := bindVarToStruct(elem, tagv, v); err != nil {
return err
}
}
}
// 最后按照默认规则进行匹配
for mapk, mapv := range paramsMap {
name := gstr.UcFirst(mapk)
if _, ok := dmap[name]; ok {
continue
}
// 后续tag逻辑中会处理的key(重复的键名)这里便不处理
if _, ok := tagmap[mapk]; !ok {
2018-09-28 13:33:41 +08:00
if err := bindVarToStruct(elem, name, mapv); err != nil {
return err
}
}
}
return nil
}
2018-09-30 13:31:03 +08:00
// 解析指针对象的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 {
if tag := field.Tag("gconv"); tag != "" {
for _, v := range strings.Split(tag, ",") {
tagmap[strings.TrimSpace(v)] = field.Name()
}
}
}
return tagmap
}
// 将参数值绑定到对象指定名称的属性上
2018-09-28 13:33:41 +08:00
func bindVarToStruct(elem reflect.Value, name string, value interface{}) error {
structFieldValue := elem.FieldByName(name)
// 键名与对象属性匹配检测map中如果有struct不存在的属性那么不做处理直接return
if !structFieldValue.IsValid() {
//return errors.New(fmt.Sprintf(`invalid struct attribute of name "%s"`, name))
return nil
}
// CanSet的属性必须为公开属性(首字母大写)
if !structFieldValue.CanSet() {
//return errors.New(fmt.Sprintf(`struct attribute of name "%s" cannot be set`, name))
return nil
}
// 必须将value转换为struct属性的数据类型这里必须用到gconv包
2018-09-29 18:06:04 +08:00
defer func() {
// 如果转换失败,那么可能是类型不匹配造成(例如属性包含自定义类型),那么执行递归转换
if recover() != nil {
bindVarToStructIfDefaultConvertionFailed(structFieldValue, value)
2018-09-29 18:06:04 +08:00
}
}()
structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))
2018-09-28 13:33:41 +08:00
return nil
}
// 将参数值绑定到对象指定索引位置的属性上
2018-09-28 13:33:41 +08:00
func bindVarToStructByIndex(elem reflect.Value, index int, value interface{}) error {
structFieldValue := elem.FieldByIndex([]int{index})
// 键名与对象属性匹配检测
if !structFieldValue.IsValid() {
//return errors.New(fmt.Sprintf("invalid struct attribute at index %d", index))
return nil
}
// CanSet的属性必须为公开属性(首字母大写)
if !structFieldValue.CanSet() {
//return errors.New(fmt.Sprintf("struct attribute cannot be set at index %d", index))
return nil
}
// 必须将value转换为struct属性的数据类型这里必须用到gconv包
2018-09-30 13:31:03 +08:00
defer func() {
// 如果转换失败,那么可能是类型不匹配造成(例如属性包含自定义类型),那么执行递归转换
if recover() != nil {
bindVarToStructIfDefaultConvertionFailed(structFieldValue, value)
2018-09-30 13:31:03 +08:00
}
}()
structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))
2018-09-28 13:33:41 +08:00
return nil
}
// 当默认的基本类型转换失败时通过recover判断后执行反射类型转换
func bindVarToStructIfDefaultConvertionFailed(structFieldValue reflect.Value, value interface{}) {
switch structFieldValue.Kind() {
case reflect.Struct:
Struct(value, structFieldValue)
case reflect.Slice:
a := reflect.Value{}
v := reflect.ValueOf(value)
if v.Kind() == reflect.Slice {
a = reflect.MakeSlice(structFieldValue.Type(), v.Len(), v.Len())
for i := 0; i < v.Len(); i++ {
n := reflect.New(structFieldValue.Type().Elem()).Elem()
Struct(v.Index(i).Interface(), n)
a.Index(i).Set(n)
}
} else {
a = reflect.MakeSlice(structFieldValue.Type(), 1, 1)
n := reflect.New(structFieldValue.Type().Elem()).Elem()
Struct(value, n)
a.Index(0).Set(n)
}
structFieldValue.Set(a)
default:
panic(errors.New(fmt.Sprintf(`cannot convert to type "%s"`, structFieldValue.Type().String())))
}
}