github.com/wI2L/jettison@v0.7.4/struct.go (about) 1 package jettison 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "reflect" 7 "sort" 8 "strings" 9 "sync" 10 "unicode" 11 ) 12 13 const validChars = "!#$%&()*+-./:<=>?@[]^_{|}~ " 14 15 var fieldsCache sync.Map // map[reflect.Type][]field 16 17 type seq struct { 18 offset uintptr 19 indir bool 20 } 21 22 type field struct { 23 typ reflect.Type 24 name string 25 keyNonEsc []byte 26 keyEscHTML []byte 27 index []int 28 tag bool 29 quoted bool 30 omitEmpty bool 31 omitNil bool 32 instr instruction 33 empty emptyFunc 34 35 // embedSeq represents the sequence of offsets 36 // and indirections to follow to reach the field 37 // through one or more anonymous fields. 38 embedSeq []seq 39 } 40 41 type typeCount map[reflect.Type]int 42 43 // byIndex sorts a list of fields by index sequence. 44 type byIndex []field 45 46 func (x byIndex) Len() int { return len(x) } 47 func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 48 49 func (x byIndex) Less(i, j int) bool { 50 for k, xik := range x[i].index { 51 if k >= len(x[j].index) { 52 return false 53 } 54 if xik != x[j].index[k] { 55 return xik < x[j].index[k] 56 } 57 } 58 return len(x[i].index) < len(x[j].index) 59 } 60 61 // cachedFields is similar to structFields, but uses a 62 // cache to avoid repeated work. 63 func cachedFields(t reflect.Type) []field { 64 if f, ok := fieldsCache.Load(t); ok { 65 return f.([]field) 66 } 67 f, _ := fieldsCache.LoadOrStore(t, structFields(t)) 68 return f.([]field) 69 } 70 71 // structFields returns a list of fields that should be 72 // encoded for the given struct type. The algorithm is 73 // breadth-first search over the set of structs to include, 74 // the top one and then any reachable anonymous structs. 75 func structFields(t reflect.Type) []field { 76 var ( 77 flds []field 78 ccnt typeCount 79 curr = []field{} 80 next = []field{{typ: t}} 81 ncnt = make(typeCount) 82 seen = make(map[reflect.Type]bool) 83 ) 84 for len(next) > 0 { 85 curr, next = next, curr[:0] 86 ccnt, ncnt = ncnt, make(map[reflect.Type]int) 87 88 for _, f := range curr { 89 if seen[f.typ] { 90 continue 91 } 92 seen[f.typ] = true 93 // Scan the type for fields to encode. 94 flds, next = scanFields(f, flds, next, ccnt, ncnt) 95 } 96 } 97 sortFields(flds) 98 99 flds = filterByVisibility(flds) 100 101 // Sort fields by their index sequence. 102 sort.Sort(byIndex(flds)) 103 104 return flds 105 } 106 107 // sortFields sorts the fields by name, breaking ties 108 // with depth, then whether the field name come from 109 // the JSON tag, and finally with the index sequence. 110 func sortFields(fields []field) { 111 sort.Slice(fields, func(i int, j int) bool { 112 x := fields 113 114 if x[i].name != x[j].name { 115 return x[i].name < x[j].name 116 } 117 if len(x[i].index) != len(x[j].index) { 118 return len(x[i].index) < len(x[j].index) 119 } 120 if x[i].tag != x[j].tag { 121 return x[i].tag 122 } 123 return byIndex(x).Less(i, j) 124 }) 125 } 126 127 // shouldEncodeField returns whether a struct 128 // field should be encoded. 129 func shouldEncodeField(sf reflect.StructField) bool { 130 isUnexported := sf.PkgPath != "" 131 if sf.Anonymous { 132 t := sf.Type 133 if t.Kind() == reflect.Ptr { 134 t = t.Elem() 135 } 136 // Ignore embedded fields of unexported non-struct 137 // types, but in the contrary, don't ignore embedded 138 // fields of unexported struct types since they may 139 // have exported fields. 140 if isUnexported && t.Kind() != reflect.Struct { 141 return false 142 } 143 } else if isUnexported { 144 // Ignore unexported non-embedded fields. 145 return false 146 } 147 return true 148 } 149 150 // isValidFieldName returns whether s is a valid 151 // name and can be used as a JSON key to encode 152 // a struct field. 153 func isValidFieldName(s string) bool { 154 if len(s) == 0 { 155 return false 156 } 157 for _, c := range s { 158 switch { 159 case strings.ContainsRune(validChars, c): 160 // Backslash and quote chars are reserved, but 161 // otherwise any punctuation chars are allowed 162 // in a tag name. 163 case !unicode.IsLetter(c) && !unicode.IsDigit(c): 164 return false 165 } 166 } 167 return true 168 } 169 170 // filterByVisibility deletes all fields that are hidden 171 // by the Go rules for embedded fields, except that fields 172 // with JSON tags are promoted. The fields are sorted in 173 // primary order of name, secondary order of field index 174 // length. 175 func filterByVisibility(fields []field) []field { 176 ret := fields[:0] 177 178 for adv, i := 0, 0; i < len(fields); i += adv { 179 // One iteration per name. 180 // Find the sequence of fields with the name 181 // of this first field. 182 fi := fields[i] 183 for adv = 1; i+adv < len(fields); adv++ { 184 fj := fields[i+adv] 185 if fj.name != fi.name { 186 break 187 } 188 } 189 if adv == 1 { 190 // Only one field with this name. 191 ret = append(ret, fi) 192 continue 193 } 194 // More than one field with the same name are 195 // present, delete hidden fields by choosing 196 // the dominant field that survives. 197 if dominant, ok := dominantField(fields[i : i+adv]); ok { 198 ret = append(ret, dominant) 199 } 200 } 201 return ret 202 } 203 204 func typeByIndex(t reflect.Type, index []int) reflect.Type { 205 for _, i := range index { 206 if t.Kind() == reflect.Ptr { 207 t = t.Elem() 208 } 209 t = t.Field(i).Type 210 } 211 return t 212 } 213 214 // dominantField looks through the fields, all of which 215 // are known to have the same name, to find the single 216 // field that dominates the others using Go's embedding 217 // rules, modified by the presence of JSON tags. If there 218 // are multiple top-level fields, it returns false. This 219 // condition is an error in Go, and all fields are skipped. 220 func dominantField(fields []field) (field, bool) { 221 if len(fields) > 1 && 222 len(fields[0].index) == len(fields[1].index) && 223 fields[0].tag == fields[1].tag { 224 return field{}, false 225 } 226 return fields[0], true 227 } 228 229 func scanFields(f field, fields, next []field, cnt, ncnt typeCount) ([]field, []field) { 230 var escBuf bytes.Buffer 231 232 for i := 0; i < f.typ.NumField(); i++ { 233 sf := f.typ.Field(i) 234 235 if !shouldEncodeField(sf) { 236 continue 237 } 238 tag := sf.Tag.Get("json") 239 if tag == "-" { 240 continue 241 } 242 // Parse name and options from the content 243 // of the JSON tag. 244 name, opts := parseTag(tag) 245 if !isValidFieldName(name) { 246 name = "" 247 } 248 index := make([]int, len(f.index)+1) 249 copy(index, f.index) 250 index[len(f.index)] = i 251 252 typ := sf.Type 253 isPtr := typ.Kind() == reflect.Ptr 254 if typ.Name() == "" && isPtr { 255 typ = typ.Elem() 256 } 257 // If the field is a named embedded struct or a 258 // simple field, record it and its index sequence. 259 if name != "" || !sf.Anonymous || typ.Kind() != reflect.Struct { 260 tagged := name != "" 261 // If a name is not present in the tag, 262 // use the struct field's name instead. 263 if name == "" { 264 name = sf.Name 265 } 266 // Build HTML escaped field key. 267 escBuf.Reset() 268 _, _ = escBuf.WriteString(`"`) 269 json.HTMLEscape(&escBuf, []byte(name)) 270 _, _ = escBuf.WriteString(`":`) 271 272 nf := field{ 273 typ: typ, 274 name: name, 275 tag: tagged, 276 index: index, 277 omitEmpty: opts.Contains("omitempty"), 278 omitNil: opts.Contains("omitnil"), 279 quoted: opts.Contains("string") && isBasicType(typ), 280 keyNonEsc: []byte(`"` + name + `":`), 281 keyEscHTML: append([]byte(nil), escBuf.Bytes()...), // copy 282 embedSeq: append(f.embedSeq[:0:0], f.embedSeq...), // clone 283 } 284 // Add final offset to sequences. 285 nf.embedSeq = append(nf.embedSeq, seq{sf.Offset, false}) 286 fields = append(fields, nf) 287 288 if cnt[f.typ] > 1 { 289 // If there were multiple instances, add a 290 // second, so that the annihilation code will 291 // see a duplicate. It only cares about the 292 // distinction between 1 or 2, so don't bother 293 // generating any more copies. 294 fields = append(fields, fields[len(fields)-1]) 295 } 296 continue 297 } 298 // Record unnamed embedded struct 299 // to be scanned in the next round. 300 ncnt[typ]++ 301 if ncnt[typ] == 1 { 302 next = append(next, field{ 303 typ: typ, 304 name: typ.Name(), 305 index: index, 306 embedSeq: append(f.embedSeq, seq{sf.Offset, isPtr}), 307 }) 308 } 309 } 310 return fields, next 311 }