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