github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/g/struct.go (about)

     1  package g
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"reflect"
     7  )
     8  
     9  // Struct encapsulates a struct type to provide several high level functions
    10  // around the struct.
    11  type Struct struct {
    12  	raw     interface{}
    13  	value   reflect.Value
    14  	TagName string
    15  }
    16  
    17  // NewStruct returns a new *Struct with the struct s. It panics if the s's kind is not struct.
    18  // Add/Remove tags tool[vscode-go] https://github.com/golang/vscode-go
    19  // the cli command https://github.com/fatih/gomodifytags
    20  // $ gomodifytags -file models/demo.go -struct Server -add-tags json,xml,struct
    21  func NewStruct(s interface{}) *Struct {
    22  	return &Struct{
    23  		raw:     s,
    24  		value:   structVal(s),
    25  		TagName: "struct", // struct's field default tag name
    26  	}
    27  }
    28  
    29  // Maps converts the given struct to a map[string]interface{}, where the keys
    30  // of the map are the field names and the values of the map the associated
    31  // values of the fields. The default key string is the struct field name but
    32  // can be changed in the struct field's tag value. The "struct" key in the
    33  // struct's field tag value is the key name. Example:
    34  //
    35  //   // Field appears in map as key "myName".
    36  //   Name string `struct:"myName"`
    37  //
    38  // A tag value with the content of "-" ignores that particular field. Example:
    39  //
    40  //   // Field is ignored by this package.
    41  //   Field bool `struct:"-"`
    42  //
    43  // A tag value with the content of "string" uses the stringer to get the value. Example:
    44  //
    45  //   // The value will be output of Animal's String() func.
    46  //   // Map will panic if Animal does not implement String().
    47  //   Field *Animal `struct:"field,string"`
    48  //
    49  // A tag value with the option of "flatten" used in a struct field is to flatten its fields
    50  // in the output map. Example:
    51  //
    52  //   // The FieldStruct's fields will be flattened into the output map.
    53  //   FieldStruct time.Time `struct:",flatten"`
    54  //
    55  // A tag value with the option of "omitnested" stops iterating further if the type
    56  // is a struct. Example:
    57  //
    58  //   // Field is not processed further by this package.
    59  //   Field time.Time     `struct:"myName,omitnested"`
    60  //   Field *http.Request `struct:",omitnested"`
    61  //
    62  // A tag value with the option of "omitempty" ignores that particular field if
    63  // the field value is empty. Example:
    64  //
    65  //   // Field appears in map as key "myName", but the field is
    66  //   // skipped if empty.
    67  //   Field string `struct:"myName,omitempty"`
    68  //
    69  //   // Field appears in map as key "Field" (the default), but
    70  //   // the field is skipped if empty.
    71  //   Field string `struct:",omitempty"`
    72  //
    73  // Note that only exported fields of a struct can be accessed, non exported
    74  // fields will be neglected.
    75  func (s *Struct) Maps() map[string]interface{} {
    76  	out := make(map[string]interface{})
    77  	s.AsMap(out)
    78  	return out
    79  }
    80  
    81  // AsMap is the same as Maps. Instead of returning the output, it fills the
    82  // given map.
    83  func (s *Struct) AsMap(out map[string]interface{}) {
    84  	if out == nil {
    85  		return
    86  	}
    87  
    88  	fields := s.structFields()
    89  
    90  	for _, field := range fields {
    91  		name := field.Name
    92  		val := s.value.FieldByName(name)
    93  		isSubStruct := false
    94  		var finalVal interface{}
    95  
    96  		tagName, tagOpts := ParseTag(field.Tag.Get(s.TagName))
    97  		if tagName != "" {
    98  			name = tagName
    99  		}
   100  
   101  		// if the value is a zero value and the field is marked as omitempty do
   102  		// not include
   103  		if tagOpts.Has("omitempty") {
   104  			zero := reflect.Zero(val.Type()).Interface()
   105  			current := val.Interface()
   106  
   107  			if reflect.DeepEqual(current, zero) {
   108  				continue
   109  			}
   110  		}
   111  
   112  		if !tagOpts.Has("omitnested") {
   113  			finalVal = s.nested(val)
   114  
   115  			v := reflect.ValueOf(val.Interface())
   116  			if v.Kind() == reflect.Ptr {
   117  				v = v.Elem()
   118  			}
   119  
   120  			switch v.Kind() {
   121  			case reflect.Map, reflect.Struct:
   122  				isSubStruct = true
   123  			}
   124  		} else {
   125  			finalVal = val.Interface()
   126  		}
   127  
   128  		if tagOpts.Has("string") {
   129  			s, ok := val.Interface().(fmt.Stringer)
   130  			if ok {
   131  				out[name] = s.String()
   132  			}
   133  			continue
   134  		}
   135  
   136  		if isSubStruct && (tagOpts.Has("flatten")) {
   137  			for k := range finalVal.(map[string]interface{}) {
   138  				out[k] = finalVal.(map[string]interface{})[k]
   139  			}
   140  		} else {
   141  			out[name] = finalVal
   142  		}
   143  	}
   144  }
   145  
   146  // Values converts the given s struct's field values to a []interface{}.  A
   147  // struct tag with the content of "-" ignores the that particular field.
   148  // Example:
   149  //
   150  //   // Field is ignored by this package.
   151  //   Field int `struct:"-"`
   152  //
   153  // A value with the option of "omitnested" stops iterating further if the type
   154  // is a struct. Example:
   155  //
   156  //   // Fields is not processed further by this package.
   157  //   Field time.Time     `struct:",omitnested"`
   158  //   Field *http.Request `struct:",omitnested"`
   159  //
   160  // A tag value with the option of "omitempty" ignores that particular field and
   161  // is not added to the values if the field value is empty. Example:
   162  //
   163  //   // Field is skipped if empty
   164  //   Field string `struct:",omitempty"`
   165  //
   166  // Note that only exported fields of a struct can be accessed, non exported
   167  // fields  will be neglected.
   168  func (s *Struct) Values() []interface{} {
   169  	fields := s.structFields()
   170  
   171  	var t []interface{}
   172  
   173  	for _, field := range fields {
   174  		val := s.value.FieldByName(field.Name)
   175  
   176  		_, tagOpts := ParseTag(field.Tag.Get(s.TagName))
   177  
   178  		// if the value is a zero value and the field is marked as omitempty do
   179  		// not include
   180  		if tagOpts.Has("omitempty") {
   181  			zero := reflect.Zero(val.Type()).Interface()
   182  			current := val.Interface()
   183  
   184  			if reflect.DeepEqual(current, zero) {
   185  				continue
   186  			}
   187  		}
   188  
   189  		if tagOpts.Has("string") {
   190  			s, ok := val.Interface().(fmt.Stringer)
   191  			if ok {
   192  				t = append(t, s.String())
   193  			}
   194  			continue
   195  		}
   196  
   197  		if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
   198  			// look out for embedded struct, and convert them to a
   199  			// []interface{} to be added to the final values slice
   200  			t = append(t, Values(val.Interface())...)
   201  		} else {
   202  			t = append(t, val.Interface())
   203  		}
   204  	}
   205  
   206  	return t
   207  }
   208  
   209  // Fields returns a slice of Fields. A struct tag with the content of "-"
   210  // ignores the checking of that particular field. Example:
   211  //
   212  //   // Field is ignored by this package.
   213  //   Field bool `struct:"-"`
   214  //
   215  // It panics if s's kind is not struct.
   216  func (s *Struct) Fields() []*Field {
   217  	return getFields(s.value, s.TagName)
   218  }
   219  
   220  // Names returns a slice of field names. A struct tag with the content of "-"
   221  // ignores the checking of that particular field. Example:
   222  //
   223  //   // Field is ignored by this package.
   224  //   Field bool `struct:"-"`
   225  //
   226  // It panics if s's kind is not struct.
   227  func (s *Struct) Names() []string {
   228  	fields := getFields(s.value, s.TagName)
   229  
   230  	names := make([]string, len(fields))
   231  
   232  	for i, field := range fields {
   233  		names[i] = field.Name()
   234  	}
   235  
   236  	return names
   237  }
   238  
   239  func getFields(v reflect.Value, tagName string) []*Field {
   240  	if v.Kind() == reflect.Ptr {
   241  		v = v.Elem()
   242  	}
   243  
   244  	t := v.Type()
   245  
   246  	var fields []*Field
   247  
   248  	for i := 0; i < t.NumField(); i++ {
   249  		field := t.Field(i)
   250  
   251  		if tag := field.Tag.Get(tagName); tag == "-" {
   252  			continue
   253  		}
   254  
   255  		f := &Field{
   256  			field: field,
   257  			value: v.FieldByName(field.Name),
   258  		}
   259  
   260  		fields = append(fields, f)
   261  
   262  	}
   263  
   264  	return fields
   265  }
   266  
   267  // Field returns a new Field struct that provides several high level functions
   268  // around a single struct field entity. It panics if the field is not found.
   269  func (s *Struct) Field(name string) *Field {
   270  	f, ok := s.FieldOk(name)
   271  	if !ok {
   272  		panic("field not found")
   273  	}
   274  
   275  	return f
   276  }
   277  
   278  // FieldOk returns a new Field struct that provides several high level functions
   279  // around a single struct field entity. The boolean returns true if the field
   280  // was found.
   281  func (s *Struct) FieldOk(name string) (*Field, bool) {
   282  	t := s.value.Type()
   283  
   284  	field, ok := t.FieldByName(name)
   285  	if !ok {
   286  		return nil, false
   287  	}
   288  
   289  	return &Field{
   290  		field:      field,
   291  		value:      s.value.FieldByName(name),
   292  		defaultTag: s.TagName,
   293  	}, true
   294  }
   295  
   296  // FieldValue returns a new Field reflect value.
   297  func (s *Struct) FieldValue(name string) reflect.Value {
   298  	f, ok := s.FieldOk(name)
   299  	if !ok {
   300  		panic("field not found")
   301  	}
   302  
   303  	return f.value
   304  }
   305  
   306  // IsZero returns true if all fields in a struct is a zero value (not
   307  // initialized) A struct tag with the content of "-" ignores the checking of
   308  // that particular field. Example:
   309  //
   310  //   // Field is ignored by this package.
   311  //   Field bool `struct:"-"`
   312  //
   313  // A value with the option of "omitnested" stops iterating further if the type
   314  // is a struct. Example:
   315  //
   316  //   // Field is not processed further by this package.
   317  //   Field time.Time     `struct:"myName,omitnested"`
   318  //   Field *http.Request `struct:",omitnested"`
   319  //
   320  // Note that only exported fields of a struct can be accessed, non exported
   321  // fields  will be neglected. It panics if s's kind is not struct.
   322  func (s *Struct) IsZero() bool {
   323  	fields := s.structFields()
   324  
   325  	for _, field := range fields {
   326  		val := s.value.FieldByName(field.Name)
   327  
   328  		_, tagOpts := ParseTag(field.Tag.Get(s.TagName))
   329  
   330  		if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
   331  			ok := IsZero(val.Interface())
   332  			if !ok {
   333  				return false
   334  			}
   335  
   336  			continue
   337  		}
   338  
   339  		// zero value of the given field, such as "" for string, 0 for int
   340  		zero := reflect.Zero(val.Type()).Interface()
   341  
   342  		//  current value of the given field
   343  		current := val.Interface()
   344  
   345  		if !reflect.DeepEqual(current, zero) {
   346  			return false
   347  		}
   348  	}
   349  
   350  	return true
   351  }
   352  
   353  // HasZero returns true if a field in a struct is not initialized (zero value).
   354  // A struct tag with the content of "-" ignores the checking of that particular
   355  // field. Example:
   356  //
   357  //   // Field is ignored by this package.
   358  //   Field bool `struct:"-"`
   359  //
   360  // A value with the option of "omitnested" stops iterating further if the type
   361  // is a struct. Example:
   362  //
   363  //   // Field is not processed further by this package.
   364  //   Field time.Time     `struct:"myName,omitnested"`
   365  //   Field *http.Request `struct:",omitnested"`
   366  //
   367  // Note that only exported fields of a struct can be accessed, non exported
   368  // fields  will be neglected. It panics if s's kind is not struct.
   369  func (s *Struct) HasZero() bool {
   370  	fields := s.structFields()
   371  
   372  	for _, field := range fields {
   373  		val := s.value.FieldByName(field.Name)
   374  
   375  		_, tagOpts := ParseTag(field.Tag.Get(s.TagName))
   376  
   377  		if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
   378  			ok := HasZero(val.Interface())
   379  			if ok {
   380  				return true
   381  			}
   382  
   383  			continue
   384  		}
   385  
   386  		// zero value of the given field, such as "" for string, 0 for int
   387  		zero := reflect.Zero(val.Type()).Interface()
   388  
   389  		//  current value of the given field
   390  		current := val.Interface()
   391  
   392  		if reflect.DeepEqual(current, zero) {
   393  			return true
   394  		}
   395  	}
   396  
   397  	return false
   398  }
   399  
   400  // Name returns the struct's type name within its package. For more info refer
   401  // to Name() function.
   402  func (s *Struct) Name() string {
   403  	return s.value.Type().Name()
   404  }
   405  
   406  // structFields returns the exported struct fields for a given s struct. This
   407  // is a convenient helper method to avoid duplicate code in some of the
   408  // functions.
   409  func (s *Struct) structFields() []reflect.StructField {
   410  	t := s.value.Type()
   411  
   412  	var f []reflect.StructField
   413  
   414  	for i := 0; i < t.NumField(); i++ {
   415  		field := t.Field(i)
   416  		// we can't access the value of unexported fields
   417  		if field.PkgPath != "" {
   418  			continue
   419  		}
   420  
   421  		// don't check if it's omitted
   422  		if tag := field.Tag.Get(s.TagName); tag == "-" {
   423  			continue
   424  		}
   425  
   426  		f = append(f, field)
   427  	}
   428  
   429  	return f
   430  }
   431  
   432  func structVal(s interface{}) (rv reflect.Value) {
   433  	if v, ok := s.(reflect.Value); ok {
   434  		rv = v
   435  	} else {
   436  		rv = reflect.ValueOf(s)
   437  	}
   438  	// if pointer get the underlying element≤
   439  	for rv.Kind() == reflect.Ptr {
   440  		rv = rv.Elem()
   441  	}
   442  	if rv.Kind() != reflect.Struct {
   443  		panic("not struct")
   444  	}
   445  	return
   446  }
   447  
   448  // Maps converts the given struct to a map[string]interface{}. For more info
   449  // refer to Struct types Map() method. It panics if s's kind is not struct.
   450  func Maps(s interface{}) map[string]interface{} {
   451  	return NewStruct(s).Maps()
   452  }
   453  
   454  // AsMap is the same as Map. Instead of returning the output, it fills the
   455  // given map.
   456  func AsMap(s interface{}, out map[string]interface{}) {
   457  	NewStruct(s).AsMap(out)
   458  }
   459  
   460  // Values converts the given struct to a []interface{}. For more info refer to
   461  // Struct types Values() method.  It panics if s's kind is not struct.
   462  func Values(s interface{}) []interface{} {
   463  	return NewStruct(s).Values()
   464  }
   465  
   466  // Fields returns a slice of *Field. For more info refer to Struct types
   467  // Fields() method.  It panics if s's kind is not struct.
   468  func Fields(s interface{}) []*Field {
   469  	return NewStruct(s).Fields()
   470  }
   471  
   472  // Names returns a slice of field names. For more info refer to Struct types
   473  // Names() method.  It panics if s's kind is not struct.
   474  func Names(s interface{}) []string {
   475  	return NewStruct(s).Names()
   476  }
   477  
   478  // IsZero returns true if all fields is equal to a zero value. For more info
   479  // refer to Struct types IsZero() method.  It panics if s's kind is not struct.
   480  func IsZero(s interface{}) bool {
   481  	return NewStruct(s).IsZero()
   482  }
   483  
   484  // HasZero returns true if any field is equal to a zero value. For more info
   485  // refer to Struct types HasZero() method.  It panics if s's kind is not struct.
   486  func HasZero(s interface{}) bool {
   487  	return NewStruct(s).HasZero()
   488  }
   489  
   490  // IsStruct returns true if the given variable is a struct or a pointer to
   491  // struct.
   492  func IsStruct(s interface{}) bool {
   493  	v := reflect.ValueOf(s)
   494  	if v.Kind() == reflect.Ptr {
   495  		v = v.Elem()
   496  	}
   497  
   498  	// uninitialized zero value of a struct
   499  	if v.Kind() == reflect.Invalid {
   500  		return false
   501  	}
   502  
   503  	return v.Kind() == reflect.Struct
   504  }
   505  
   506  // Name returns the struct's type name within its package. It returns an
   507  // empty string for unnamed types. It panics if s's kind is not struct.
   508  func Name(s interface{}) string {
   509  	return NewStruct(s).Name()
   510  }
   511  
   512  // nested retrieves recursively all types for the given value and returns the
   513  // nested value.
   514  func (s *Struct) nested(val reflect.Value) interface{} {
   515  	var finalVal interface{}
   516  
   517  	v := reflect.ValueOf(val.Interface())
   518  	if v.Kind() == reflect.Ptr {
   519  		v = v.Elem()
   520  	}
   521  
   522  	switch v.Kind() {
   523  	case reflect.Struct:
   524  		n := NewStruct(val.Interface())
   525  		n.TagName = s.TagName
   526  		m := n.Maps()
   527  
   528  		// do not add the converted value if there are no exported fields, ie:
   529  		// time.Time
   530  		if len(m) == 0 {
   531  			finalVal = val.Interface()
   532  		} else {
   533  			finalVal = m
   534  		}
   535  	case reflect.Map:
   536  		// get the element type of the map
   537  		mapElem := val.Type()
   538  		switch val.Type().Kind() {
   539  		case reflect.Ptr, reflect.Array, reflect.Map,
   540  			reflect.Slice, reflect.Chan:
   541  			mapElem = val.Type().Elem()
   542  			if mapElem.Kind() == reflect.Ptr {
   543  				mapElem = mapElem.Elem()
   544  			}
   545  		}
   546  
   547  		// only iterate over struct types, ie: map[string]StructType,
   548  		// map[string][]StructType,
   549  		if mapElem.Kind() == reflect.Struct ||
   550  			(mapElem.Kind() == reflect.Slice &&
   551  				mapElem.Elem().Kind() == reflect.Struct) {
   552  			m := make(map[string]interface{}, val.Len())
   553  			for _, k := range val.MapKeys() {
   554  				m[k.String()] = s.nested(val.MapIndex(k))
   555  			}
   556  			finalVal = m
   557  			break
   558  		}
   559  
   560  		finalVal = val.Interface()
   561  	case reflect.Slice, reflect.Array:
   562  		if val.Type().Kind() == reflect.Interface {
   563  			finalVal = val.Interface()
   564  			break
   565  		}
   566  
   567  		// do not iterate of non struct types, just pass the value. Ie: []int,
   568  		// []string, co... We only iterate further if it's a struct.
   569  		// i.e []foo or []*foo
   570  		if val.Type().Elem().Kind() != reflect.Struct &&
   571  			!(val.Type().Elem().Kind() == reflect.Ptr &&
   572  				val.Type().Elem().Elem().Kind() == reflect.Struct) {
   573  			finalVal = val.Interface()
   574  			break
   575  		}
   576  
   577  		slices := make([]interface{}, val.Len())
   578  		for x := 0; x < val.Len(); x++ {
   579  			slices[x] = s.nested(val.Index(x))
   580  		}
   581  		finalVal = slices
   582  	default:
   583  		finalVal = val.Interface()
   584  	}
   585  
   586  	return finalVal
   587  }