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  }