mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
encoding/json: handle anonymous fields
Fixes #3069. R=golang-dev, n13m3y3r CC=golang-dev https://golang.org/cl/6460044
This commit is contained in:
parent
56e1384aa0
commit
f97bfb93f4
3 changed files with 392 additions and 138 deletions
|
|
@ -493,56 +493,39 @@ func (d *decodeState) object(v reflect.Value) {
|
||||||
}
|
}
|
||||||
subv = mapElem
|
subv = mapElem
|
||||||
} else {
|
} else {
|
||||||
var f reflect.StructField
|
var f *field
|
||||||
var ok bool
|
fields := cachedTypeFields(sv.Type())
|
||||||
st := sv.Type()
|
for i := range fields {
|
||||||
for i := 0; i < sv.NumField(); i++ {
|
ff := &fields[i]
|
||||||
sf := st.Field(i)
|
if ff.name == key {
|
||||||
tag := sf.Tag.Get("json")
|
f = ff
|
||||||
if tag == "-" {
|
break
|
||||||
// Pretend this field doesn't exist.
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if sf.Anonymous {
|
if f == nil && strings.EqualFold(ff.name, key) {
|
||||||
// Pretend this field doesn't exist,
|
f = ff
|
||||||
// so that we can do a good job with
|
|
||||||
// these in a later version.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// First, tag match
|
|
||||||
tagName, _ := parseTag(tag)
|
|
||||||
if tagName != "" {
|
|
||||||
if tagName == key {
|
|
||||||
f = sf
|
|
||||||
ok = true
|
|
||||||
break // no better match possible
|
|
||||||
}
|
|
||||||
// There was a tag, but it didn't match.
|
|
||||||
// Ignore field names.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Second, exact field name match
|
|
||||||
if sf.Name == key {
|
|
||||||
f = sf
|
|
||||||
ok = true
|
|
||||||
}
|
|
||||||
// Third, case-insensitive field name match,
|
|
||||||
// but only if a better match hasn't already been seen
|
|
||||||
if !ok && strings.EqualFold(sf.Name, key) {
|
|
||||||
f = sf
|
|
||||||
ok = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if f != nil {
|
||||||
// Extract value; name must be exported.
|
subv = sv
|
||||||
if ok {
|
destring = f.quoted
|
||||||
if f.PkgPath != "" {
|
for _, i := range f.index {
|
||||||
d.saveError(&UnmarshalFieldError{key, st, f})
|
if subv.Kind() == reflect.Ptr {
|
||||||
} else {
|
if subv.IsNil() {
|
||||||
subv = sv.FieldByIndex(f.Index)
|
subv.Set(reflect.New(subv.Type().Elem()))
|
||||||
|
}
|
||||||
|
subv = subv.Elem()
|
||||||
|
}
|
||||||
|
subv = subv.Field(i)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// To give a good error, a quick scan for unexported fields in top level.
|
||||||
|
st := sv.Type()
|
||||||
|
for i := 0; i < st.NumField(); i++ {
|
||||||
|
f := st.Field(i)
|
||||||
|
if f.PkgPath != "" && strings.EqualFold(f.Name, key) {
|
||||||
|
d.saveError(&UnmarshalFieldError{key, st, f})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_, opts := parseTag(f.Tag.Get("json"))
|
|
||||||
destring = opts.Contains("string")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1005,11 +988,3 @@ func unquoteBytes(s []byte) (t []byte, ok bool) {
|
||||||
}
|
}
|
||||||
return b[0:w], true
|
return b[0:w], true
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following is issue 3069.
|
|
||||||
|
|
||||||
// BUG(rsc): This package ignores anonymous (embedded) struct fields
|
|
||||||
// during encoding and decoding. A future version may assign meaning
|
|
||||||
// to them. To force an anonymous field to be ignored in all future
|
|
||||||
// versions of this package, use an explicit `json:"-"` tag in the struct
|
|
||||||
// definition.
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ package json
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -74,6 +75,100 @@ var (
|
||||||
umstruct = ustruct{unmarshaler{true}}
|
umstruct = ustruct{unmarshaler{true}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Test data structures for anonymous fields.
|
||||||
|
|
||||||
|
type Point struct {
|
||||||
|
Z int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Top struct {
|
||||||
|
Level0 int
|
||||||
|
Embed0
|
||||||
|
*Embed0a
|
||||||
|
*Embed0b `json:"e,omitempty"` // treated as named
|
||||||
|
Embed0c `json:"-"` // ignored
|
||||||
|
Loop
|
||||||
|
Embed0p // has Point with X, Y, used
|
||||||
|
Embed0q // has Point with Z, used
|
||||||
|
}
|
||||||
|
|
||||||
|
type Embed0 struct {
|
||||||
|
Level1a int // overridden by Embed0a's Level1a with json tag
|
||||||
|
Level1b int // used because Embed0a's Level1b is renamed
|
||||||
|
Level1c int // used because Embed0a's Level1c is ignored
|
||||||
|
Level1d int // annihilated by Embed0a's Level1d
|
||||||
|
Level1e int `json:"x"` // annihilated by Embed0a.Level1e
|
||||||
|
}
|
||||||
|
|
||||||
|
type Embed0a struct {
|
||||||
|
Level1a int `json:"Level1a,omitempty"`
|
||||||
|
Level1b int `json:"LEVEL1B,omitempty"`
|
||||||
|
Level1c int `json:"-"`
|
||||||
|
Level1d int // annihilated by Embed0's Level1d
|
||||||
|
Level1f int `json:"x"` // annihilated by Embed0's Level1e
|
||||||
|
}
|
||||||
|
|
||||||
|
type Embed0b Embed0
|
||||||
|
|
||||||
|
type Embed0c Embed0
|
||||||
|
|
||||||
|
type Embed0p struct {
|
||||||
|
image.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
type Embed0q struct {
|
||||||
|
Point
|
||||||
|
}
|
||||||
|
|
||||||
|
type Loop struct {
|
||||||
|
Loop1 int `json:",omitempty"`
|
||||||
|
Loop2 int `json:",omitempty"`
|
||||||
|
*Loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// From reflect test:
|
||||||
|
// The X in S6 and S7 annihilate, but they also block the X in S8.S9.
|
||||||
|
type S5 struct {
|
||||||
|
S6
|
||||||
|
S7
|
||||||
|
S8
|
||||||
|
}
|
||||||
|
|
||||||
|
type S6 struct {
|
||||||
|
X int
|
||||||
|
}
|
||||||
|
|
||||||
|
type S7 S6
|
||||||
|
|
||||||
|
type S8 struct {
|
||||||
|
S9
|
||||||
|
}
|
||||||
|
|
||||||
|
type S9 struct {
|
||||||
|
X int
|
||||||
|
Y int
|
||||||
|
}
|
||||||
|
|
||||||
|
// From reflect test:
|
||||||
|
// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9.
|
||||||
|
type S10 struct {
|
||||||
|
S11
|
||||||
|
S12
|
||||||
|
S13
|
||||||
|
}
|
||||||
|
|
||||||
|
type S11 struct {
|
||||||
|
S6
|
||||||
|
}
|
||||||
|
|
||||||
|
type S12 struct {
|
||||||
|
S6
|
||||||
|
}
|
||||||
|
|
||||||
|
type S13 struct {
|
||||||
|
S8
|
||||||
|
}
|
||||||
|
|
||||||
type unmarshalTest struct {
|
type unmarshalTest struct {
|
||||||
in string
|
in string
|
||||||
ptr interface{}
|
ptr interface{}
|
||||||
|
|
@ -82,6 +177,12 @@ type unmarshalTest struct {
|
||||||
useNumber bool
|
useNumber bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Ambig struct {
|
||||||
|
// Given "hello", the first match should win.
|
||||||
|
First int `json:"HELLO"`
|
||||||
|
Second int `json:"Hello"`
|
||||||
|
}
|
||||||
|
|
||||||
var unmarshalTests = []unmarshalTest{
|
var unmarshalTests = []unmarshalTest{
|
||||||
// basic types
|
// basic types
|
||||||
{in: `true`, ptr: new(bool), out: true},
|
{in: `true`, ptr: new(bool), out: true},
|
||||||
|
|
@ -137,6 +238,74 @@ var unmarshalTests = []unmarshalTest{
|
||||||
{in: `[{"T":false}]`, ptr: &umslice, out: umslice},
|
{in: `[{"T":false}]`, ptr: &umslice, out: umslice},
|
||||||
{in: `[{"T":false}]`, ptr: &umslicep, out: &umslice},
|
{in: `[{"T":false}]`, ptr: &umslicep, out: &umslice},
|
||||||
{in: `{"M":{"T":false}}`, ptr: &umstruct, out: umstruct},
|
{in: `{"M":{"T":false}}`, ptr: &umstruct, out: umstruct},
|
||||||
|
|
||||||
|
{
|
||||||
|
in: `{
|
||||||
|
"Level0": 1,
|
||||||
|
"Level1b": 2,
|
||||||
|
"Level1c": 3,
|
||||||
|
"x": 4,
|
||||||
|
"Level1a": 5,
|
||||||
|
"LEVEL1B": 6,
|
||||||
|
"e": {
|
||||||
|
"Level1a": 8,
|
||||||
|
"Level1b": 9,
|
||||||
|
"Level1c": 10,
|
||||||
|
"Level1d": 11,
|
||||||
|
"x": 12
|
||||||
|
},
|
||||||
|
"Loop1": 13,
|
||||||
|
"Loop2": 14,
|
||||||
|
"X": 15,
|
||||||
|
"Y": 16,
|
||||||
|
"Z": 17
|
||||||
|
}`,
|
||||||
|
ptr: new(Top),
|
||||||
|
out: Top{
|
||||||
|
Level0: 1,
|
||||||
|
Embed0: Embed0{
|
||||||
|
Level1b: 2,
|
||||||
|
Level1c: 3,
|
||||||
|
},
|
||||||
|
Embed0a: &Embed0a{
|
||||||
|
Level1a: 5,
|
||||||
|
Level1b: 6,
|
||||||
|
},
|
||||||
|
Embed0b: &Embed0b{
|
||||||
|
Level1a: 8,
|
||||||
|
Level1b: 9,
|
||||||
|
Level1c: 10,
|
||||||
|
Level1d: 11,
|
||||||
|
Level1e: 12,
|
||||||
|
},
|
||||||
|
Loop: Loop{
|
||||||
|
Loop1: 13,
|
||||||
|
Loop2: 14,
|
||||||
|
},
|
||||||
|
Embed0p: Embed0p{
|
||||||
|
Point: image.Point{X: 15, Y: 16},
|
||||||
|
},
|
||||||
|
Embed0q: Embed0q{
|
||||||
|
Point: Point{Z: 17},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: `{"hello": 1}`,
|
||||||
|
ptr: new(Ambig),
|
||||||
|
out: Ambig{First: 1},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
in: `{"X": 1,"Y":2}`,
|
||||||
|
ptr: new(S5),
|
||||||
|
out: S5{S8: S8{S9: S9{Y: 2}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: `{"X": 1,"Y":2}`,
|
||||||
|
ptr: new(S10),
|
||||||
|
out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarshal(t *testing.T) {
|
func TestMarshal(t *testing.T) {
|
||||||
|
|
@ -720,35 +889,6 @@ func TestRefUnmarshal(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that anonymous fields are ignored.
|
|
||||||
// We may assign meaning to them later.
|
|
||||||
func TestAnonymous(t *testing.T) {
|
|
||||||
type S struct {
|
|
||||||
T
|
|
||||||
N int
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := Marshal(new(S))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Marshal: %v", err)
|
|
||||||
}
|
|
||||||
want := `{"N":0}`
|
|
||||||
if string(data) != want {
|
|
||||||
t.Fatalf("Marshal = %#q, want %#q", string(data), want)
|
|
||||||
}
|
|
||||||
|
|
||||||
var s S
|
|
||||||
if err := Unmarshal([]byte(`{"T": 1, "T": {"Y": 1}, "N": 2}`), &s); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
if s.N != 2 {
|
|
||||||
t.Fatal("Unmarshal: did not set N")
|
|
||||||
}
|
|
||||||
if s.T.Y != 0 {
|
|
||||||
t.Fatal("Unmarshal: did set T.Y")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that the empty string doesn't panic decoding when ,string is specified
|
// Test that the empty string doesn't panic decoding when ,string is specified
|
||||||
// Issue 3450
|
// Issue 3450
|
||||||
func TestEmptyString(t *testing.T) {
|
func TestEmptyString(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,16 @@ import (
|
||||||
// only Unicode letters, digits, dollar signs, percent signs, hyphens,
|
// only Unicode letters, digits, dollar signs, percent signs, hyphens,
|
||||||
// underscores and slashes.
|
// underscores and slashes.
|
||||||
//
|
//
|
||||||
|
// Anonymous struct fields are usually marshaled as if their inner exported fields
|
||||||
|
// were fields in the outer struct, subject to the usual Go visibility rules.
|
||||||
|
// An anonymous struct field with a name given in its JSON tag is treated as
|
||||||
|
// having that name instead of as anonymous.
|
||||||
|
//
|
||||||
|
// Handling of anonymous struct fields is new in Go 1.1.
|
||||||
|
// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of
|
||||||
|
// an anonymous struct field in both current and earlier versions, give the field
|
||||||
|
// a JSON tag of "-".
|
||||||
|
//
|
||||||
// Map values encode as JSON objects.
|
// Map values encode as JSON objects.
|
||||||
// The map's key type must be string; the object keys are used directly
|
// The map's key type must be string; the object keys are used directly
|
||||||
// as map keys.
|
// as map keys.
|
||||||
|
|
@ -333,9 +343,9 @@ func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
e.WriteByte('{')
|
e.WriteByte('{')
|
||||||
first := true
|
first := true
|
||||||
for _, ef := range encodeFields(v.Type()) {
|
for _, f := range cachedTypeFields(v.Type()) {
|
||||||
fieldValue := v.Field(ef.i)
|
fv := fieldByIndex(v, f.index)
|
||||||
if ef.omitEmpty && isEmptyValue(fieldValue) {
|
if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if first {
|
if first {
|
||||||
|
|
@ -343,9 +353,9 @@ func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) {
|
||||||
} else {
|
} else {
|
||||||
e.WriteByte(',')
|
e.WriteByte(',')
|
||||||
}
|
}
|
||||||
e.string(ef.tag)
|
e.string(f.name)
|
||||||
e.WriteByte(':')
|
e.WriteByte(':')
|
||||||
e.reflectValueQuoted(fieldValue, ef.quoted)
|
e.reflectValueQuoted(fv, f.quoted)
|
||||||
}
|
}
|
||||||
e.WriteByte('}')
|
e.WriteByte('}')
|
||||||
|
|
||||||
|
|
@ -440,6 +450,19 @@ func isValidTag(s string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fieldByIndex(v reflect.Value, index []int) reflect.Value {
|
||||||
|
for _, i := range index {
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
if v.IsNil() {
|
||||||
|
return reflect.Value{}
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
v = v.Field(i)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
|
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
|
||||||
// It implements the methods to sort by string.
|
// It implements the methods to sort by string.
|
||||||
type stringValues []reflect.Value
|
type stringValues []reflect.Value
|
||||||
|
|
@ -498,67 +521,183 @@ func (e *encodeState) string(s string) (int, error) {
|
||||||
return e.Len() - len0, nil
|
return e.Len() - len0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeField contains information about how to encode a field of a
|
// A field represents a single field found in a struct.
|
||||||
// struct.
|
type field struct {
|
||||||
type encodeField struct {
|
name string
|
||||||
i int // field index in struct
|
tag bool
|
||||||
tag string
|
index []int
|
||||||
quoted bool
|
typ reflect.Type
|
||||||
omitEmpty bool
|
omitEmpty bool
|
||||||
|
quoted bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// byName sorts field by name, breaking ties with depth,
|
||||||
typeCacheLock sync.RWMutex
|
// then breaking ties with "name came from json tag", then
|
||||||
encodeFieldsCache = make(map[reflect.Type][]encodeField)
|
// breaking ties with index sequence.
|
||||||
)
|
type byName []field
|
||||||
|
|
||||||
// encodeFields returns a slice of encodeField for a given
|
func (x byName) Len() int { return len(x) }
|
||||||
// struct type.
|
|
||||||
func encodeFields(t reflect.Type) []encodeField {
|
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
typeCacheLock.RLock()
|
|
||||||
fs, ok := encodeFieldsCache[t]
|
func (x byName) Less(i, j int) bool {
|
||||||
typeCacheLock.RUnlock()
|
if x[i].name != x[j].name {
|
||||||
if ok {
|
return x[i].name < x[j].name
|
||||||
return fs
|
|
||||||
}
|
}
|
||||||
|
if len(x[i].index) != len(x[j].index) {
|
||||||
typeCacheLock.Lock()
|
return len(x[i].index) < len(x[j].index)
|
||||||
defer typeCacheLock.Unlock()
|
|
||||||
fs, ok = encodeFieldsCache[t]
|
|
||||||
if ok {
|
|
||||||
return fs
|
|
||||||
}
|
}
|
||||||
|
if x[i].tag != x[j].tag {
|
||||||
|
return x[i].tag
|
||||||
|
}
|
||||||
|
return byIndex(x).Less(i, j)
|
||||||
|
}
|
||||||
|
|
||||||
v := reflect.Zero(t)
|
// byIndex sorts field by index sequence.
|
||||||
n := v.NumField()
|
type byIndex []field
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
f := t.Field(i)
|
|
||||||
if f.PkgPath != "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if f.Anonymous {
|
|
||||||
// We want to do a better job with these later,
|
|
||||||
// so for now pretend they don't exist.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var ef encodeField
|
|
||||||
ef.i = i
|
|
||||||
ef.tag = f.Name
|
|
||||||
|
|
||||||
tv := f.Tag.Get("json")
|
func (x byIndex) Len() int { return len(x) }
|
||||||
if tv != "" {
|
|
||||||
if tv == "-" {
|
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
|
||||||
|
func (x byIndex) Less(i, j int) bool {
|
||||||
|
for k, xik := range x[i].index {
|
||||||
|
if k >= len(x[j].index) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if xik != x[j].index[k] {
|
||||||
|
return xik < x[j].index[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(x[i].index) < len(x[j].index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeFields returns a list of fields that JSON should recognize for the given type.
|
||||||
|
// The algorithm is breadth-first search over the set of structs to include - the top struct
|
||||||
|
// and then any reachable anonymous structs.
|
||||||
|
func typeFields(t reflect.Type) []field {
|
||||||
|
// Anonymous fields to explore at the current level and the next.
|
||||||
|
current := []field{}
|
||||||
|
next := []field{{typ: t}}
|
||||||
|
|
||||||
|
// Count of queued names for current level and the next.
|
||||||
|
count := map[reflect.Type]int{}
|
||||||
|
nextCount := map[reflect.Type]int{}
|
||||||
|
|
||||||
|
// Types already visited at an earlier level.
|
||||||
|
visited := map[reflect.Type]bool{}
|
||||||
|
|
||||||
|
// Fields found.
|
||||||
|
var fields []field
|
||||||
|
|
||||||
|
for len(next) > 0 {
|
||||||
|
current, next = next, current[:0]
|
||||||
|
count, nextCount = nextCount, map[reflect.Type]int{}
|
||||||
|
|
||||||
|
for _, f := range current {
|
||||||
|
if visited[f.typ] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name, opts := parseTag(tv)
|
visited[f.typ] = true
|
||||||
if isValidTag(name) {
|
|
||||||
ef.tag = name
|
// Scan f.typ for fields to include.
|
||||||
|
for i := 0; i < f.typ.NumField(); i++ {
|
||||||
|
sf := f.typ.Field(i)
|
||||||
|
if sf.PkgPath != "" { // unexported
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tag := sf.Tag.Get("json")
|
||||||
|
if tag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, opts := parseTag(tag)
|
||||||
|
if !isValidTag(name) {
|
||||||
|
name = ""
|
||||||
|
}
|
||||||
|
index := make([]int, len(f.index)+1)
|
||||||
|
copy(index, f.index)
|
||||||
|
index[len(f.index)] = i
|
||||||
|
// Record found field and index sequence.
|
||||||
|
if name != "" || !sf.Anonymous {
|
||||||
|
tagged := name != ""
|
||||||
|
if name == "" {
|
||||||
|
name = sf.Name
|
||||||
|
}
|
||||||
|
fields = append(fields, field{name, tagged, index, sf.Type,
|
||||||
|
opts.Contains("omitempty"), opts.Contains("string")})
|
||||||
|
if count[f.typ] > 1 {
|
||||||
|
// If there were multiple instances, add a second,
|
||||||
|
// so that the annihilation code will see a duplicate.
|
||||||
|
// It only cares about the distinction between 1 or 2,
|
||||||
|
// so don't bother generating any more copies.
|
||||||
|
fields = append(fields, fields[len(fields)-1])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record new anonymous struct to explore in next round.
|
||||||
|
ft := sf.Type
|
||||||
|
if ft.Name() == "" {
|
||||||
|
// Must be pointer.
|
||||||
|
ft = ft.Elem()
|
||||||
|
}
|
||||||
|
nextCount[ft]++
|
||||||
|
if nextCount[ft] == 1 {
|
||||||
|
next = append(next, field{name: ft.Name(), index: index, typ: ft})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ef.omitEmpty = opts.Contains("omitempty")
|
|
||||||
ef.quoted = opts.Contains("string")
|
|
||||||
}
|
}
|
||||||
fs = append(fs, ef)
|
|
||||||
}
|
}
|
||||||
encodeFieldsCache[t] = fs
|
|
||||||
return fs
|
sort.Sort(byName(fields))
|
||||||
|
|
||||||
|
// Remove fields with annihilating name collisions
|
||||||
|
// and also fields shadowed by fields with explicit JSON tags.
|
||||||
|
name := ""
|
||||||
|
out := fields[:0]
|
||||||
|
for _, f := range fields {
|
||||||
|
if f.name != name {
|
||||||
|
name = f.name
|
||||||
|
out = append(out, f)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n := len(out); n > 0 && out[n-1].name == name && (!out[n-1].tag || f.tag) {
|
||||||
|
out = out[:n-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields = out
|
||||||
|
|
||||||
|
sort.Sort(byIndex(fields))
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
m map[reflect.Type][]field
|
||||||
|
}
|
||||||
|
|
||||||
|
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||||
|
func cachedTypeFields(t reflect.Type) []field {
|
||||||
|
fieldCache.RLock()
|
||||||
|
f := fieldCache.m[t]
|
||||||
|
fieldCache.RUnlock()
|
||||||
|
if f != nil {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fields without lock.
|
||||||
|
// Might duplicate effort but won't hold other computations back.
|
||||||
|
f = typeFields(t)
|
||||||
|
if f == nil {
|
||||||
|
f = []field{}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldCache.Lock()
|
||||||
|
if fieldCache.m == nil {
|
||||||
|
fieldCache.m = map[reflect.Type][]field{}
|
||||||
|
}
|
||||||
|
fieldCache.m[t] = f
|
||||||
|
fieldCache.Unlock()
|
||||||
|
return f
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue