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  }