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