mirror of
https://github.com/gogf/gf.git
synced 2025-04-05 03:05:05 +08:00
267 lines
7.5 KiB
Go
267 lines
7.5 KiB
Go
// 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 gstructs provides functions for struct information retrieving.
|
|
package gstructs
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
)
|
|
|
|
// Type wraps reflect.Type for additional features.
|
|
type Type struct {
|
|
reflect.Type
|
|
}
|
|
|
|
// Field contains information of a struct field .
|
|
type Field struct {
|
|
Value reflect.Value // The underlying value of the field.
|
|
Field reflect.StructField // The underlying field of the field.
|
|
|
|
// Retrieved tag name. It depends TagValue.
|
|
TagName string
|
|
|
|
// Retrieved tag value.
|
|
// There might be more than one tags in the field,
|
|
// but only one can be retrieved according to calling function rules.
|
|
TagValue string
|
|
}
|
|
|
|
// FieldsInput is the input parameter struct type for function Fields.
|
|
type FieldsInput struct {
|
|
// Pointer should be type of struct/*struct.
|
|
// TODO this attribute name is not suitable, which would make confuse.
|
|
Pointer any
|
|
|
|
// RecursiveOption specifies the way retrieving the fields recursively if the attribute
|
|
// is an embedded struct. It is RecursiveOptionNone in default.
|
|
RecursiveOption RecursiveOption
|
|
}
|
|
|
|
// FieldMapInput is the input parameter struct type for function FieldMap.
|
|
type FieldMapInput struct {
|
|
// Pointer should be type of struct/*struct.
|
|
// TODO this attribute name is not suitable, which would make confuse.
|
|
Pointer any
|
|
|
|
// PriorityTagArray specifies the priority tag array for retrieving from high to low.
|
|
// If it's given `nil`, it returns map[name]Field, of which the `name` is attribute name.
|
|
PriorityTagArray []string
|
|
|
|
// RecursiveOption specifies the way retrieving the fields recursively if the attribute
|
|
// is an embedded struct. It is RecursiveOptionNone in default.
|
|
RecursiveOption RecursiveOption
|
|
}
|
|
|
|
type RecursiveOption int
|
|
|
|
const (
|
|
RecursiveOptionNone RecursiveOption = iota // No recursively retrieving fields as map if the field is an embedded struct.
|
|
RecursiveOptionEmbedded // Recursively retrieving fields as map if the field is an embedded struct.
|
|
RecursiveOptionEmbeddedNoTag // Recursively retrieving fields as map if the field is an embedded struct and the field has no tag.
|
|
)
|
|
|
|
// Fields retrieves and returns the fields of `pointer` as slice.
|
|
func Fields(in FieldsInput) ([]Field, error) {
|
|
var (
|
|
ok bool
|
|
fieldFilterMap = make(map[string]struct{})
|
|
retrievedFields = make([]Field, 0)
|
|
currentLevelFieldMap = make(map[string]Field)
|
|
rangeFields, err = getFieldValues(in.Pointer)
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for index := 0; index < len(rangeFields); index++ {
|
|
field := rangeFields[index]
|
|
currentLevelFieldMap[field.Name()] = field
|
|
}
|
|
|
|
for index := 0; index < len(rangeFields); index++ {
|
|
field := rangeFields[index]
|
|
if _, ok = fieldFilterMap[field.Name()]; ok {
|
|
continue
|
|
}
|
|
if field.IsEmbedded() {
|
|
if in.RecursiveOption != RecursiveOptionNone {
|
|
switch in.RecursiveOption {
|
|
case RecursiveOptionEmbeddedNoTag:
|
|
if field.TagStr() != "" {
|
|
break
|
|
}
|
|
fallthrough
|
|
|
|
case RecursiveOptionEmbedded:
|
|
structFields, err := Fields(FieldsInput{
|
|
Pointer: field.Value,
|
|
RecursiveOption: in.RecursiveOption,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// The current level fields can overwrite the sub-struct fields with the same name.
|
|
for i := 0; i < len(structFields); i++ {
|
|
var (
|
|
structField = structFields[i]
|
|
fieldName = structField.Name()
|
|
)
|
|
if _, ok = fieldFilterMap[fieldName]; ok {
|
|
continue
|
|
}
|
|
fieldFilterMap[fieldName] = struct{}{}
|
|
if v, ok := currentLevelFieldMap[fieldName]; !ok {
|
|
retrievedFields = append(retrievedFields, structField)
|
|
} else {
|
|
retrievedFields = append(retrievedFields, v)
|
|
}
|
|
}
|
|
continue
|
|
default:
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
fieldFilterMap[field.Name()] = struct{}{}
|
|
retrievedFields = append(retrievedFields, field)
|
|
}
|
|
return retrievedFields, nil
|
|
}
|
|
|
|
// FieldMap retrieves and returns struct field as map[name/tag]Field from `pointer`.
|
|
//
|
|
// The parameter `pointer` should be type of struct/*struct.
|
|
//
|
|
// The parameter `priority` specifies the priority tag array for retrieving from high to low.
|
|
// If it's given `nil`, it returns map[name]Field, of which the `name` is attribute name.
|
|
//
|
|
// The parameter `recursive` specifies whether retrieving the fields recursively if the attribute
|
|
// is an embedded struct.
|
|
//
|
|
// Note that it only retrieves the exported attributes with first letter upper-case from struct.
|
|
func FieldMap(in FieldMapInput) (map[string]Field, error) {
|
|
fields, err := getFieldValues(in.Pointer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var (
|
|
tagValue string
|
|
mapField = make(map[string]Field)
|
|
)
|
|
for _, field := range fields {
|
|
// Only retrieve exported attributes.
|
|
if !field.IsExported() {
|
|
continue
|
|
}
|
|
tagValue = ""
|
|
for _, p := range in.PriorityTagArray {
|
|
tagValue = field.Tag(p)
|
|
if tagValue != "" && tagValue != "-" {
|
|
break
|
|
}
|
|
}
|
|
tempField := field
|
|
tempField.TagValue = tagValue
|
|
if tagValue != "" {
|
|
mapField[tagValue] = tempField
|
|
} else {
|
|
if in.RecursiveOption != RecursiveOptionNone && field.IsEmbedded() {
|
|
switch in.RecursiveOption {
|
|
case RecursiveOptionEmbeddedNoTag:
|
|
if field.TagStr() != "" {
|
|
mapField[field.Name()] = tempField
|
|
break
|
|
}
|
|
fallthrough
|
|
|
|
case RecursiveOptionEmbedded:
|
|
m, err := FieldMap(FieldMapInput{
|
|
Pointer: field.Value,
|
|
PriorityTagArray: in.PriorityTagArray,
|
|
RecursiveOption: in.RecursiveOption,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for k, v := range m {
|
|
if _, ok := mapField[k]; !ok {
|
|
tempV := v
|
|
mapField[k] = tempV
|
|
}
|
|
}
|
|
default:
|
|
}
|
|
} else {
|
|
mapField[field.Name()] = tempField
|
|
}
|
|
}
|
|
}
|
|
return mapField, nil
|
|
}
|
|
|
|
// StructType retrieves and returns the struct Type of specified struct/*struct.
|
|
// The parameter `object` should be either type of struct/*struct/[]struct/[]*struct.
|
|
func StructType(object any) (*Type, error) {
|
|
// if already reflect.Type
|
|
if reflectType, ok := object.(reflect.Type); ok {
|
|
for reflectType.Kind() == reflect.Ptr {
|
|
reflectType = reflectType.Elem()
|
|
}
|
|
if reflectType.Kind() == reflect.Struct {
|
|
return &Type{
|
|
Type: reflectType,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
var (
|
|
reflectValue reflect.Value
|
|
reflectKind reflect.Kind
|
|
reflectType reflect.Type
|
|
)
|
|
if rv, ok := object.(reflect.Value); ok {
|
|
reflectValue = rv
|
|
} else {
|
|
reflectValue = reflect.ValueOf(object)
|
|
}
|
|
reflectKind = reflectValue.Kind()
|
|
for {
|
|
switch reflectKind {
|
|
case reflect.Ptr:
|
|
if !reflectValue.IsValid() || reflectValue.IsNil() {
|
|
// If pointer is type of *struct and nil, then automatically create a temporary struct.
|
|
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
|
|
reflectKind = reflectValue.Kind()
|
|
} else {
|
|
reflectValue = reflectValue.Elem()
|
|
reflectKind = reflectValue.Kind()
|
|
}
|
|
|
|
case reflect.Array, reflect.Slice:
|
|
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
|
|
reflectKind = reflectValue.Kind()
|
|
|
|
default:
|
|
goto exitLoop
|
|
}
|
|
}
|
|
|
|
exitLoop:
|
|
if reflectKind != reflect.Struct {
|
|
return nil, gerror.Newf(
|
|
`invalid object kind "%s", kind of "struct" is required`,
|
|
reflectKind,
|
|
)
|
|
}
|
|
reflectType = reflectValue.Type()
|
|
return &Type{
|
|
Type: reflectType,
|
|
}, nil
|
|
}
|