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