2
0

struct_reflect.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package common
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. // StructToMap 递归地把任意结构体 v 转成 map[string]any。
  7. // - 只处理导出字段;未导出字段会被跳过。
  8. // - 优先使用 `json:"name"` 里逗号前的部分作为键;如果是 "-" 则忽略该字段;若无 tag,则使用字段名。
  9. // - 对指针、切片、数组、嵌套结构体、map 做深度遍历,保持原始结构。
  10. func StructToMap(v any) (map[string]any, error) {
  11. val := reflect.ValueOf(v)
  12. if !val.IsValid() {
  13. return nil, fmt.Errorf("nil value")
  14. }
  15. for val.Kind() == reflect.Pointer {
  16. if val.IsNil() {
  17. return nil, fmt.Errorf("nil pointer")
  18. }
  19. val = val.Elem()
  20. }
  21. if val.Kind() != reflect.Struct {
  22. return nil, fmt.Errorf("expect struct, got %s", val.Kind())
  23. }
  24. return structValueToMap(val), nil
  25. }
  26. func structValueToMap(val reflect.Value) map[string]any {
  27. out := make(map[string]any, val.NumField())
  28. typ := val.Type()
  29. for i := 0; i < val.NumField(); i++ {
  30. f := typ.Field(i)
  31. if f.PkgPath != "" { // 未导出字段
  32. continue
  33. }
  34. // 解析 json tag
  35. tag := f.Tag.Get("json")
  36. name, opts := parseTag(tag)
  37. if name == "-" {
  38. continue
  39. }
  40. if name == "" {
  41. name = f.Name
  42. }
  43. fv := val.Field(i)
  44. out[name] = valueToAny(fv, opts.Contains("omitempty"))
  45. }
  46. return out
  47. }
  48. // valueToAny 递归处理各种值类型。
  49. func valueToAny(v reflect.Value, omitEmpty bool) any {
  50. if !v.IsValid() {
  51. return nil
  52. }
  53. for v.Kind() == reflect.Pointer {
  54. if v.IsNil() {
  55. if omitEmpty {
  56. return nil
  57. }
  58. // 保持与 encoding/json 行为一致,nil 指针写成 null
  59. return nil
  60. }
  61. v = v.Elem()
  62. }
  63. switch v.Kind() {
  64. case reflect.Struct:
  65. return structValueToMap(v)
  66. case reflect.Slice, reflect.Array:
  67. l := v.Len()
  68. arr := make([]any, l)
  69. for i := 0; i < l; i++ {
  70. arr[i] = valueToAny(v.Index(i), false)
  71. }
  72. return arr
  73. case reflect.Map:
  74. m := make(map[string]any, v.Len())
  75. iter := v.MapRange()
  76. for iter.Next() {
  77. k := iter.Key()
  78. // 只支持 string key,与 encoding/json 一致
  79. if k.Kind() == reflect.String {
  80. m[k.String()] = valueToAny(iter.Value(), false)
  81. }
  82. }
  83. return m
  84. default:
  85. // 基本类型直接返回其接口值
  86. return v.Interface()
  87. }
  88. }
  89. // tagOptions 用于判断是否包含 "omitempty"
  90. type tagOptions string
  91. func (o tagOptions) Contains(opt string) bool {
  92. if len(o) == 0 {
  93. return false
  94. }
  95. for _, s := range splitComma(string(o)) {
  96. if s == opt {
  97. return true
  98. }
  99. }
  100. return false
  101. }
  102. func parseTag(tag string) (string, tagOptions) {
  103. if idx := indexComma(tag); idx != -1 {
  104. return tag[:idx], tagOptions(tag[idx+1:])
  105. }
  106. return tag, tagOptions("")
  107. }
  108. // 避免 strings.Split 额外分配
  109. func indexComma(s string) int {
  110. for i, r := range s {
  111. if r == ',' {
  112. return i
  113. }
  114. }
  115. return -1
  116. }
  117. func splitComma(s string) []string {
  118. var parts []string
  119. start := 0
  120. for i, r := range s {
  121. if r == ',' {
  122. parts = append(parts, s[start:i])
  123. start = i + 1
  124. }
  125. }
  126. if start <= len(s) {
  127. parts = append(parts, s[start:])
  128. }
  129. return parts
  130. }