cache.go (2541B)
1 package form 2 3 import ( 4 "reflect" 5 "sort" 6 "strings" 7 "sync" 8 "sync/atomic" 9 ) 10 11 type cacheFields []cachedField 12 13 func (s cacheFields) Len() int { 14 return len(s) 15 } 16 17 func (s cacheFields) Less(i, j int) bool { 18 return !s[i].isAnonymous 19 } 20 21 func (s cacheFields) Swap(i, j int) { 22 s[i], s[j] = s[j], s[i] 23 } 24 25 type cachedField struct { 26 idx int 27 name string 28 isAnonymous bool 29 isOmitEmpty bool 30 } 31 32 type cachedStruct struct { 33 fields cacheFields 34 } 35 36 type structCacheMap struct { 37 m atomic.Value // map[reflect.Type]*cachedStruct 38 lock sync.Mutex 39 tagFn TagNameFunc 40 } 41 42 // TagNameFunc allows for adding of a custom tag name parser 43 type TagNameFunc func(field reflect.StructField) string 44 45 func newStructCacheMap() *structCacheMap { 46 47 sc := new(structCacheMap) 48 sc.m.Store(make(map[reflect.Type]*cachedStruct)) 49 50 return sc 51 } 52 53 func (s *structCacheMap) Get(key reflect.Type) (value *cachedStruct, ok bool) { 54 value, ok = s.m.Load().(map[reflect.Type]*cachedStruct)[key] 55 return 56 } 57 58 func (s *structCacheMap) Set(key reflect.Type, value *cachedStruct) { 59 60 m := s.m.Load().(map[reflect.Type]*cachedStruct) 61 62 nm := make(map[reflect.Type]*cachedStruct, len(m)+1) 63 for k, v := range m { 64 nm[k] = v 65 } 66 nm[key] = value 67 s.m.Store(nm) 68 } 69 70 func (s *structCacheMap) parseStruct(mode Mode, current reflect.Value, key reflect.Type, tagName string) *cachedStruct { 71 72 s.lock.Lock() 73 74 // could have been multiple trying to access, but once first is done this ensures struct 75 // isn't parsed again. 76 cs, ok := s.Get(key) 77 if ok { 78 s.lock.Unlock() 79 return cs 80 } 81 82 typ := current.Type() 83 cs = &cachedStruct{fields: make([]cachedField, 0, 4)} // init 4, betting most structs decoding into have at aleast 4 fields. 84 85 numFields := current.NumField() 86 87 var fld reflect.StructField 88 var name string 89 var idx int 90 var isOmitEmpty bool 91 92 for i := 0; i < numFields; i++ { 93 isOmitEmpty = false 94 fld = typ.Field(i) 95 96 if fld.PkgPath != blank && !fld.Anonymous { 97 continue 98 } 99 100 if s.tagFn != nil { 101 name = s.tagFn(fld) 102 } else { 103 name = fld.Tag.Get(tagName) 104 } 105 106 if name == ignore { 107 continue 108 } 109 110 if mode == ModeExplicit && len(name) == 0 { 111 continue 112 } 113 114 // check for omitempty 115 if idx = strings.LastIndexByte(name, ','); idx != -1 { 116 isOmitEmpty = name[idx+1:] == "omitempty" 117 name = name[:idx] 118 } 119 120 if len(name) == 0 { 121 name = fld.Name 122 } 123 124 cs.fields = append(cs.fields, cachedField{idx: i, name: name, isAnonymous: fld.Anonymous, isOmitEmpty: isOmitEmpty}) 125 } 126 127 sort.Sort(cs.fields) 128 s.Set(typ, cs) 129 130 s.lock.Unlock() 131 132 return cs 133 }