structs.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. // Package structs contains various utilities functions to work with structs.
  2. package structs
  3. import (
  4. "fmt"
  5. "reflect"
  6. )
  7. var (
  8. // DefaultTagName is the default tag name for struct fields which provides
  9. // a more granular to tweak certain structs. Lookup the necessary functions
  10. // for more info.
  11. DefaultTagName = "structs" // struct's field default tag name
  12. )
  13. // Struct encapsulates a struct type to provide several high level functions
  14. // around the struct.
  15. type Struct struct {
  16. raw interface{}
  17. value reflect.Value
  18. TagName string
  19. }
  20. // New returns a new *Struct with the struct s. It panics if the s's kind is
  21. // not struct.
  22. func New(s interface{}) *Struct {
  23. return &Struct{
  24. raw: s,
  25. value: strctVal(s),
  26. TagName: DefaultTagName,
  27. }
  28. }
  29. // Map 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 "structs" 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 `structs:"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 `structs:"-"`
  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 `structs:"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 `structs:",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 `structs:"myName,omitnested"`
  60. // Field *http.Request `structs:",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 `structs:"myName,omitempty"`
  68. //
  69. // // Field appears in map as key "Field" (the default), but
  70. // // the field is skipped if empty.
  71. // Field string `structs:",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) Map() map[string]interface{} {
  76. out := make(map[string]interface{})
  77. s.FillMap(out)
  78. return out
  79. }
  80. // FillMap is the same as Map. Instead of returning the output, it fills the
  81. // given map.
  82. func (s *Struct) FillMap(out map[string]interface{}) {
  83. if out == nil {
  84. return
  85. }
  86. fields := s.structFields()
  87. for _, field := range fields {
  88. name := field.Name
  89. val := s.value.FieldByName(name)
  90. isSubStruct := false
  91. var finalVal interface{}
  92. tagName, tagOpts := parseTag(field.Tag.Get(s.TagName))
  93. if tagName != "" {
  94. name = tagName
  95. }
  96. // if the value is a zero value and the field is marked as omitempty do
  97. // not include
  98. if tagOpts.Has("omitempty") {
  99. zero := reflect.Zero(val.Type()).Interface()
  100. current := val.Interface()
  101. if reflect.DeepEqual(current, zero) {
  102. continue
  103. }
  104. }
  105. if !tagOpts.Has("omitnested") {
  106. finalVal = s.nested(val)
  107. v := reflect.ValueOf(val.Interface())
  108. if v.Kind() == reflect.Ptr {
  109. v = v.Elem()
  110. }
  111. switch v.Kind() {
  112. case reflect.Map, reflect.Struct:
  113. isSubStruct = true
  114. }
  115. } else {
  116. finalVal = val.Interface()
  117. }
  118. if tagOpts.Has("string") {
  119. s, ok := val.Interface().(fmt.Stringer)
  120. if ok {
  121. out[name] = s.String()
  122. }
  123. continue
  124. }
  125. if isSubStruct && (tagOpts.Has("flatten")) {
  126. for k := range finalVal.(map[string]interface{}) {
  127. out[k] = finalVal.(map[string]interface{})[k]
  128. }
  129. } else {
  130. out[name] = finalVal
  131. }
  132. }
  133. }
  134. // Values converts the given s struct's field values to a []interface{}. A
  135. // struct tag with the content of "-" ignores the that particular field.
  136. // Example:
  137. //
  138. // // Field is ignored by this package.
  139. // Field int `structs:"-"`
  140. //
  141. // A value with the option of "omitnested" stops iterating further if the type
  142. // is a struct. Example:
  143. //
  144. // // Fields is not processed further by this package.
  145. // Field time.Time `structs:",omitnested"`
  146. // Field *http.Request `structs:",omitnested"`
  147. //
  148. // A tag value with the option of "omitempty" ignores that particular field and
  149. // is not added to the values if the field value is empty. Example:
  150. //
  151. // // Field is skipped if empty
  152. // Field string `structs:",omitempty"`
  153. //
  154. // Note that only exported fields of a struct can be accessed, non exported
  155. // fields will be neglected.
  156. func (s *Struct) Values() []interface{} {
  157. fields := s.structFields()
  158. var t []interface{}
  159. for _, field := range fields {
  160. val := s.value.FieldByName(field.Name)
  161. _, tagOpts := parseTag(field.Tag.Get(s.TagName))
  162. // if the value is a zero value and the field is marked as omitempty do
  163. // not include
  164. if tagOpts.Has("omitempty") {
  165. zero := reflect.Zero(val.Type()).Interface()
  166. current := val.Interface()
  167. if reflect.DeepEqual(current, zero) {
  168. continue
  169. }
  170. }
  171. if tagOpts.Has("string") {
  172. s, ok := val.Interface().(fmt.Stringer)
  173. if ok {
  174. t = append(t, s.String())
  175. }
  176. continue
  177. }
  178. if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
  179. // look out for embedded structs, and convert them to a
  180. // []interface{} to be added to the final values slice
  181. t = append(t, Values(val.Interface())...)
  182. } else {
  183. t = append(t, val.Interface())
  184. }
  185. }
  186. return t
  187. }
  188. // Fields returns a slice of Fields. A struct tag with the content of "-"
  189. // ignores the checking of that particular field. Example:
  190. //
  191. // // Field is ignored by this package.
  192. // Field bool `structs:"-"`
  193. //
  194. // It panics if s's kind is not struct.
  195. func (s *Struct) Fields() []*Field {
  196. return getFields(s.value, s.TagName)
  197. }
  198. // Names returns a slice of field names. A struct tag with the content of "-"
  199. // ignores the checking of that particular field. Example:
  200. //
  201. // // Field is ignored by this package.
  202. // Field bool `structs:"-"`
  203. //
  204. // It panics if s's kind is not struct.
  205. func (s *Struct) Names() []string {
  206. fields := getFields(s.value, s.TagName)
  207. names := make([]string, len(fields))
  208. for i, field := range fields {
  209. names[i] = field.Name()
  210. }
  211. return names
  212. }
  213. func getFields(v reflect.Value, tagName string) []*Field {
  214. if v.Kind() == reflect.Ptr {
  215. v = v.Elem()
  216. }
  217. t := v.Type()
  218. var fields []*Field
  219. for i := 0; i < t.NumField(); i++ {
  220. field := t.Field(i)
  221. if tag := field.Tag.Get(tagName); tag == "-" {
  222. continue
  223. }
  224. f := &Field{
  225. field: field,
  226. value: v.FieldByName(field.Name),
  227. }
  228. fields = append(fields, f)
  229. }
  230. return fields
  231. }
  232. // Field returns a new Field struct that provides several high level functions
  233. // around a single struct field entity. It panics if the field is not found.
  234. func (s *Struct) Field(name string) *Field {
  235. f, ok := s.FieldOk(name)
  236. if !ok {
  237. panic("field not found")
  238. }
  239. return f
  240. }
  241. // FieldOk returns a new Field struct that provides several high level functions
  242. // around a single struct field entity. The boolean returns true if the field
  243. // was found.
  244. func (s *Struct) FieldOk(name string) (*Field, bool) {
  245. t := s.value.Type()
  246. field, ok := t.FieldByName(name)
  247. if !ok {
  248. return nil, false
  249. }
  250. return &Field{
  251. field: field,
  252. value: s.value.FieldByName(name),
  253. defaultTag: s.TagName,
  254. }, true
  255. }
  256. // IsZero returns true if all fields in a struct is a zero value (not
  257. // initialized) A struct tag with the content of "-" ignores the checking of
  258. // that particular field. Example:
  259. //
  260. // // Field is ignored by this package.
  261. // Field bool `structs:"-"`
  262. //
  263. // A value with the option of "omitnested" stops iterating further if the type
  264. // is a struct. Example:
  265. //
  266. // // Field is not processed further by this package.
  267. // Field time.Time `structs:"myName,omitnested"`
  268. // Field *http.Request `structs:",omitnested"`
  269. //
  270. // Note that only exported fields of a struct can be accessed, non exported
  271. // fields will be neglected. It panics if s's kind is not struct.
  272. func (s *Struct) IsZero() bool {
  273. fields := s.structFields()
  274. for _, field := range fields {
  275. val := s.value.FieldByName(field.Name)
  276. _, tagOpts := parseTag(field.Tag.Get(s.TagName))
  277. if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
  278. ok := IsZero(val.Interface())
  279. if !ok {
  280. return false
  281. }
  282. continue
  283. }
  284. // zero value of the given field, such as "" for string, 0 for int
  285. zero := reflect.Zero(val.Type()).Interface()
  286. // current value of the given field
  287. current := val.Interface()
  288. if !reflect.DeepEqual(current, zero) {
  289. return false
  290. }
  291. }
  292. return true
  293. }
  294. // HasZero returns true if a field in a struct is not initialized (zero value).
  295. // A struct tag with the content of "-" ignores the checking of that particular
  296. // field. Example:
  297. //
  298. // // Field is ignored by this package.
  299. // Field bool `structs:"-"`
  300. //
  301. // A value with the option of "omitnested" stops iterating further if the type
  302. // is a struct. Example:
  303. //
  304. // // Field is not processed further by this package.
  305. // Field time.Time `structs:"myName,omitnested"`
  306. // Field *http.Request `structs:",omitnested"`
  307. //
  308. // Note that only exported fields of a struct can be accessed, non exported
  309. // fields will be neglected. It panics if s's kind is not struct.
  310. func (s *Struct) HasZero() bool {
  311. fields := s.structFields()
  312. for _, field := range fields {
  313. val := s.value.FieldByName(field.Name)
  314. _, tagOpts := parseTag(field.Tag.Get(s.TagName))
  315. if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
  316. ok := HasZero(val.Interface())
  317. if ok {
  318. return true
  319. }
  320. continue
  321. }
  322. // zero value of the given field, such as "" for string, 0 for int
  323. zero := reflect.Zero(val.Type()).Interface()
  324. // current value of the given field
  325. current := val.Interface()
  326. if reflect.DeepEqual(current, zero) {
  327. return true
  328. }
  329. }
  330. return false
  331. }
  332. // Name returns the structs's type name within its package. For more info refer
  333. // to Name() function.
  334. func (s *Struct) Name() string {
  335. return s.value.Type().Name()
  336. }
  337. // structFields returns the exported struct fields for a given s struct. This
  338. // is a convenient helper method to avoid duplicate code in some of the
  339. // functions.
  340. func (s *Struct) structFields() []reflect.StructField {
  341. t := s.value.Type()
  342. var f []reflect.StructField
  343. for i := 0; i < t.NumField(); i++ {
  344. field := t.Field(i)
  345. // we can't access the value of unexported fields
  346. if field.PkgPath != "" {
  347. continue
  348. }
  349. // don't check if it's omitted
  350. if tag := field.Tag.Get(s.TagName); tag == "-" {
  351. continue
  352. }
  353. f = append(f, field)
  354. }
  355. return f
  356. }
  357. func strctVal(s interface{}) reflect.Value {
  358. v := reflect.ValueOf(s)
  359. // if pointer get the underlying element≤
  360. for v.Kind() == reflect.Ptr {
  361. v = v.Elem()
  362. }
  363. if v.Kind() != reflect.Struct {
  364. panic("not struct")
  365. }
  366. return v
  367. }
  368. // Map converts the given struct to a map[string]interface{}. For more info
  369. // refer to Struct types Map() method. It panics if s's kind is not struct.
  370. func Map(s interface{}) map[string]interface{} {
  371. return New(s).Map()
  372. }
  373. // FillMap is the same as Map. Instead of returning the output, it fills the
  374. // given map.
  375. func FillMap(s interface{}, out map[string]interface{}) {
  376. New(s).FillMap(out)
  377. }
  378. // Values converts the given struct to a []interface{}. For more info refer to
  379. // Struct types Values() method. It panics if s's kind is not struct.
  380. func Values(s interface{}) []interface{} {
  381. return New(s).Values()
  382. }
  383. // Fields returns a slice of *Field. For more info refer to Struct types
  384. // Fields() method. It panics if s's kind is not struct.
  385. func Fields(s interface{}) []*Field {
  386. return New(s).Fields()
  387. }
  388. // Names returns a slice of field names. For more info refer to Struct types
  389. // Names() method. It panics if s's kind is not struct.
  390. func Names(s interface{}) []string {
  391. return New(s).Names()
  392. }
  393. // IsZero returns true if all fields is equal to a zero value. For more info
  394. // refer to Struct types IsZero() method. It panics if s's kind is not struct.
  395. func IsZero(s interface{}) bool {
  396. return New(s).IsZero()
  397. }
  398. // HasZero returns true if any field is equal to a zero value. For more info
  399. // refer to Struct types HasZero() method. It panics if s's kind is not struct.
  400. func HasZero(s interface{}) bool {
  401. return New(s).HasZero()
  402. }
  403. // IsStruct returns true if the given variable is a struct or a pointer to
  404. // struct.
  405. func IsStruct(s interface{}) bool {
  406. v := reflect.ValueOf(s)
  407. if v.Kind() == reflect.Ptr {
  408. v = v.Elem()
  409. }
  410. // uninitialized zero value of a struct
  411. if v.Kind() == reflect.Invalid {
  412. return false
  413. }
  414. return v.Kind() == reflect.Struct
  415. }
  416. // Name returns the structs's type name within its package. It returns an
  417. // empty string for unnamed types. It panics if s's kind is not struct.
  418. func Name(s interface{}) string {
  419. return New(s).Name()
  420. }
  421. // nested retrieves recursively all types for the given value and returns the
  422. // nested value.
  423. func (s *Struct) nested(val reflect.Value) interface{} {
  424. var finalVal interface{}
  425. v := reflect.ValueOf(val.Interface())
  426. if v.Kind() == reflect.Ptr {
  427. v = v.Elem()
  428. }
  429. switch v.Kind() {
  430. case reflect.Struct:
  431. n := New(val.Interface())
  432. n.TagName = s.TagName
  433. m := n.Map()
  434. // do not add the converted value if there are no exported fields, ie:
  435. // time.Time
  436. if len(m) == 0 {
  437. finalVal = val.Interface()
  438. } else {
  439. finalVal = m
  440. }
  441. case reflect.Map:
  442. // get the element type of the map
  443. mapElem := val.Type()
  444. switch val.Type().Kind() {
  445. case reflect.Ptr, reflect.Array, reflect.Map,
  446. reflect.Slice, reflect.Chan:
  447. mapElem = val.Type().Elem()
  448. if mapElem.Kind() == reflect.Ptr {
  449. mapElem = mapElem.Elem()
  450. }
  451. }
  452. // only iterate over struct types, ie: map[string]StructType,
  453. // map[string][]StructType,
  454. if mapElem.Kind() == reflect.Struct ||
  455. (mapElem.Kind() == reflect.Slice &&
  456. mapElem.Elem().Kind() == reflect.Struct) {
  457. m := make(map[string]interface{}, val.Len())
  458. for _, k := range val.MapKeys() {
  459. m[k.String()] = s.nested(val.MapIndex(k))
  460. }
  461. finalVal = m
  462. break
  463. }
  464. // TODO(arslan): should this be optional?
  465. finalVal = val.Interface()
  466. case reflect.Slice, reflect.Array:
  467. if val.Type().Kind() == reflect.Interface {
  468. finalVal = val.Interface()
  469. break
  470. }
  471. // TODO(arslan): should this be optional?
  472. // do not iterate of non struct types, just pass the value. Ie: []int,
  473. // []string, co... We only iterate further if it's a struct.
  474. // i.e []foo or []*foo
  475. if val.Type().Elem().Kind() != reflect.Struct &&
  476. !(val.Type().Elem().Kind() == reflect.Ptr &&
  477. val.Type().Elem().Elem().Kind() == reflect.Struct) {
  478. finalVal = val.Interface()
  479. break
  480. }
  481. slices := make([]interface{}, val.Len())
  482. for x := 0; x < val.Len(); x++ {
  483. slices[x] = s.nested(val.Index(x))
  484. }
  485. finalVal = slices
  486. default:
  487. finalVal = val.Interface()
  488. }
  489. return finalVal
  490. }