structs.go raw

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