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