reflect.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. package gocsv
  2. import (
  3. "reflect"
  4. "strings"
  5. "sync"
  6. )
  7. // --------------------------------------------------------------------------
  8. // Reflection helpers
  9. type structInfo struct {
  10. Fields []fieldInfo
  11. }
  12. // fieldInfo is a struct field that should be mapped to a CSV column, or vice-versa
  13. // Each IndexChain element before the last is the index of an the embedded struct field
  14. // that defines Key as a tag
  15. type fieldInfo struct {
  16. keys []string
  17. omitEmpty bool
  18. IndexChain []int
  19. }
  20. func (f fieldInfo) getFirstKey() string {
  21. return f.keys[0]
  22. }
  23. func (f fieldInfo) matchesKey(key string) bool {
  24. for _, k := range f.keys {
  25. if key == k || strings.TrimSpace(key) == k {
  26. return true
  27. }
  28. }
  29. return false
  30. }
  31. var structMap = make(map[reflect.Type]*structInfo)
  32. var structMapMutex sync.RWMutex
  33. func getStructInfo(rType reflect.Type) *structInfo {
  34. structMapMutex.RLock()
  35. stInfo, ok := structMap[rType]
  36. structMapMutex.RUnlock()
  37. if ok {
  38. return stInfo
  39. }
  40. fieldsList := getFieldInfos(rType, []int{})
  41. stInfo = &structInfo{fieldsList}
  42. return stInfo
  43. }
  44. func getFieldInfos(rType reflect.Type, parentIndexChain []int) []fieldInfo {
  45. fieldsCount := rType.NumField()
  46. fieldsList := make([]fieldInfo, 0, fieldsCount)
  47. for i := 0; i < fieldsCount; i++ {
  48. field := rType.Field(i)
  49. if field.PkgPath != "" {
  50. continue
  51. }
  52. indexChain := append(parentIndexChain, i)
  53. // if the field is an embedded struct, create a fieldInfo for each of its fields
  54. if field.Anonymous && field.Type.Kind() == reflect.Struct {
  55. fieldsList = append(fieldsList, getFieldInfos(field.Type, indexChain)...)
  56. continue
  57. }
  58. fieldInfo := fieldInfo{IndexChain: indexChain}
  59. fieldTag := field.Tag.Get("csv")
  60. fieldTags := strings.Split(fieldTag, TagSeparator)
  61. filteredTags := []string{}
  62. for _, fieldTagEntry := range fieldTags {
  63. if fieldTagEntry != "omitempty" {
  64. filteredTags = append(filteredTags, fieldTagEntry)
  65. } else {
  66. fieldInfo.omitEmpty = true
  67. }
  68. }
  69. if len(filteredTags) == 1 && filteredTags[0] == "-" {
  70. continue
  71. } else if len(filteredTags) > 0 && filteredTags[0] != "" {
  72. fieldInfo.keys = filteredTags
  73. } else {
  74. fieldInfo.keys = []string{field.Name}
  75. }
  76. fieldsList = append(fieldsList, fieldInfo)
  77. }
  78. return fieldsList
  79. }
  80. func getConcreteContainerInnerType(in reflect.Type) (inInnerWasPointer bool, inInnerType reflect.Type) {
  81. inInnerType = in.Elem()
  82. inInnerWasPointer = false
  83. if inInnerType.Kind() == reflect.Ptr {
  84. inInnerWasPointer = true
  85. inInnerType = inInnerType.Elem()
  86. }
  87. return inInnerWasPointer, inInnerType
  88. }
  89. func getConcreteReflectValueAndType(in interface{}) (reflect.Value, reflect.Type) {
  90. value := reflect.ValueOf(in)
  91. if value.Kind() == reflect.Ptr {
  92. value = value.Elem()
  93. }
  94. return value, value.Type()
  95. }