github.com/gogf/gf/v2@v2.7.4/os/gstructs/gstructs.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  // Package gstructs provides functions for struct information retrieving.
     8  package gstructs
     9  
    10  import (
    11  	"reflect"
    12  
    13  	"github.com/gogf/gf/v2/errors/gerror"
    14  )
    15  
    16  // Type wraps reflect.Type for additional features.
    17  type Type struct {
    18  	reflect.Type
    19  }
    20  
    21  // Field contains information of a struct field .
    22  type Field struct {
    23  	Value reflect.Value       // The underlying value of the field.
    24  	Field reflect.StructField // The underlying field of the field.
    25  
    26  	// Retrieved tag name. It depends TagValue.
    27  	TagName string
    28  
    29  	// Retrieved tag value.
    30  	// There might be more than one tags in the field,
    31  	// but only one can be retrieved according to calling function rules.
    32  	TagValue string
    33  }
    34  
    35  // FieldsInput is the input parameter struct type for function Fields.
    36  type FieldsInput struct {
    37  	// Pointer should be type of struct/*struct.
    38  	// TODO this attribute name is not suitable, which would make confuse.
    39  	Pointer interface{}
    40  
    41  	// RecursiveOption specifies the way retrieving the fields recursively if the attribute
    42  	// is an embedded struct. It is RecursiveOptionNone in default.
    43  	RecursiveOption RecursiveOption
    44  }
    45  
    46  // FieldMapInput is the input parameter struct type for function FieldMap.
    47  type FieldMapInput struct {
    48  	// Pointer should be type of struct/*struct.
    49  	// TODO this attribute name is not suitable, which would make confuse.
    50  	Pointer interface{}
    51  
    52  	// PriorityTagArray specifies the priority tag array for retrieving from high to low.
    53  	// If it's given `nil`, it returns map[name]Field, of which the `name` is attribute name.
    54  	PriorityTagArray []string
    55  
    56  	// RecursiveOption specifies the way retrieving the fields recursively if the attribute
    57  	// is an embedded struct. It is RecursiveOptionNone in default.
    58  	RecursiveOption RecursiveOption
    59  }
    60  
    61  type RecursiveOption int
    62  
    63  const (
    64  	RecursiveOptionNone          RecursiveOption = iota // No recursively retrieving fields as map if the field is an embedded struct.
    65  	RecursiveOptionEmbedded                             // Recursively retrieving fields as map if the field is an embedded struct.
    66  	RecursiveOptionEmbeddedNoTag                        // Recursively retrieving fields as map if the field is an embedded struct and the field has no tag.
    67  )
    68  
    69  // Fields retrieves and returns the fields of `pointer` as slice.
    70  func Fields(in FieldsInput) ([]Field, error) {
    71  	var (
    72  		ok                   bool
    73  		fieldFilterMap       = make(map[string]struct{})
    74  		retrievedFields      = make([]Field, 0)
    75  		currentLevelFieldMap = make(map[string]Field)
    76  		rangeFields, err     = getFieldValues(in.Pointer)
    77  	)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	for index := 0; index < len(rangeFields); index++ {
    83  		field := rangeFields[index]
    84  		currentLevelFieldMap[field.Name()] = field
    85  	}
    86  
    87  	for index := 0; index < len(rangeFields); index++ {
    88  		field := rangeFields[index]
    89  		if _, ok = fieldFilterMap[field.Name()]; ok {
    90  			continue
    91  		}
    92  		if field.IsEmbedded() {
    93  			if in.RecursiveOption != RecursiveOptionNone {
    94  				switch in.RecursiveOption {
    95  				case RecursiveOptionEmbeddedNoTag:
    96  					if field.TagStr() != "" {
    97  						break
    98  					}
    99  					fallthrough
   100  
   101  				case RecursiveOptionEmbedded:
   102  					structFields, err := Fields(FieldsInput{
   103  						Pointer:         field.Value,
   104  						RecursiveOption: in.RecursiveOption,
   105  					})
   106  					if err != nil {
   107  						return nil, err
   108  					}
   109  					// The current level fields can overwrite the sub-struct fields with the same name.
   110  					for i := 0; i < len(structFields); i++ {
   111  						var (
   112  							structField = structFields[i]
   113  							fieldName   = structField.Name()
   114  						)
   115  						if _, ok = fieldFilterMap[fieldName]; ok {
   116  							continue
   117  						}
   118  						fieldFilterMap[fieldName] = struct{}{}
   119  						if v, ok := currentLevelFieldMap[fieldName]; !ok {
   120  							retrievedFields = append(retrievedFields, structField)
   121  						} else {
   122  							retrievedFields = append(retrievedFields, v)
   123  						}
   124  					}
   125  					continue
   126  				}
   127  			}
   128  			continue
   129  		}
   130  		fieldFilterMap[field.Name()] = struct{}{}
   131  		retrievedFields = append(retrievedFields, field)
   132  	}
   133  	return retrievedFields, nil
   134  }
   135  
   136  // FieldMap retrieves and returns struct field as map[name/tag]Field from `pointer`.
   137  //
   138  // The parameter `pointer` should be type of struct/*struct.
   139  //
   140  // The parameter `priority` specifies the priority tag array for retrieving from high to low.
   141  // If it's given `nil`, it returns map[name]Field, of which the `name` is attribute name.
   142  //
   143  // The parameter `recursive` specifies whether retrieving the fields recursively if the attribute
   144  // is an embedded struct.
   145  //
   146  // Note that it only retrieves the exported attributes with first letter upper-case from struct.
   147  func FieldMap(in FieldMapInput) (map[string]Field, error) {
   148  	fields, err := getFieldValues(in.Pointer)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	var (
   153  		tagValue string
   154  		mapField = make(map[string]Field)
   155  	)
   156  	for _, field := range fields {
   157  		// Only retrieve exported attributes.
   158  		if !field.IsExported() {
   159  			continue
   160  		}
   161  		tagValue = ""
   162  		for _, p := range in.PriorityTagArray {
   163  			tagValue = field.Tag(p)
   164  			if tagValue != "" && tagValue != "-" {
   165  				break
   166  			}
   167  		}
   168  		tempField := field
   169  		tempField.TagValue = tagValue
   170  		if tagValue != "" {
   171  			mapField[tagValue] = tempField
   172  		} else {
   173  			if in.RecursiveOption != RecursiveOptionNone && field.IsEmbedded() {
   174  				switch in.RecursiveOption {
   175  				case RecursiveOptionEmbeddedNoTag:
   176  					if field.TagStr() != "" {
   177  						mapField[field.Name()] = tempField
   178  						break
   179  					}
   180  					fallthrough
   181  
   182  				case RecursiveOptionEmbedded:
   183  					m, err := FieldMap(FieldMapInput{
   184  						Pointer:          field.Value,
   185  						PriorityTagArray: in.PriorityTagArray,
   186  						RecursiveOption:  in.RecursiveOption,
   187  					})
   188  					if err != nil {
   189  						return nil, err
   190  					}
   191  					for k, v := range m {
   192  						if _, ok := mapField[k]; !ok {
   193  							tempV := v
   194  							mapField[k] = tempV
   195  						}
   196  					}
   197  				}
   198  			} else {
   199  				mapField[field.Name()] = tempField
   200  			}
   201  		}
   202  	}
   203  	return mapField, nil
   204  }
   205  
   206  // StructType retrieves and returns the struct Type of specified struct/*struct.
   207  // The parameter `object` should be either type of struct/*struct/[]struct/[]*struct.
   208  func StructType(object interface{}) (*Type, error) {
   209  	var (
   210  		reflectValue reflect.Value
   211  		reflectKind  reflect.Kind
   212  		reflectType  reflect.Type
   213  	)
   214  	if rv, ok := object.(reflect.Value); ok {
   215  		reflectValue = rv
   216  	} else {
   217  		reflectValue = reflect.ValueOf(object)
   218  	}
   219  	reflectKind = reflectValue.Kind()
   220  	for {
   221  		switch reflectKind {
   222  		case reflect.Ptr:
   223  			if !reflectValue.IsValid() || reflectValue.IsNil() {
   224  				// If pointer is type of *struct and nil, then automatically create a temporary struct.
   225  				reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
   226  				reflectKind = reflectValue.Kind()
   227  			} else {
   228  				reflectValue = reflectValue.Elem()
   229  				reflectKind = reflectValue.Kind()
   230  			}
   231  
   232  		case reflect.Array, reflect.Slice:
   233  			reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
   234  			reflectKind = reflectValue.Kind()
   235  
   236  		default:
   237  			goto exitLoop
   238  		}
   239  	}
   240  
   241  exitLoop:
   242  	if reflectKind != reflect.Struct {
   243  		return nil, gerror.Newf(
   244  			`invalid object kind "%s", kind of "struct" is required`,
   245  			reflectKind,
   246  		)
   247  	}
   248  	reflectType = reflectValue.Type()
   249  	return &Type{
   250  		Type: reflectType,
   251  	}, nil
   252  }