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