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