1// Package structs 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 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.
81func (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.
89func (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.
174func (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.
222func (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.
233func (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
245func 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.
275func (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.
287func (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.
318func (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.
365func (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.
398func (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.
405func (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
428func 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.
445func 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.
451func 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.
457func 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.
463func 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.
469func 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.
475func 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.
481func 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.
487func 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.
503func 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.
509func (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