1
0
mirror of https://github.com/json-iterator/go.git synced 2025-07-15 23:54:21 +02:00

fix anonymous struct

This commit is contained in:
Tao Wen
2017-06-20 13:33:40 +08:00
parent be221df432
commit 8f6a840c63
4 changed files with 169 additions and 67 deletions

View File

@ -16,14 +16,22 @@ var extensions = []Extension{}
type StructDescriptor struct { type StructDescriptor struct {
Type reflect.Type Type reflect.Type
Fields map[string]*Binding Fields []*Binding
}
func (structDescriptor *StructDescriptor) GetField(fieldName string) *Binding {
for _, binding := range structDescriptor.Fields {
if binding.Field.Name == fieldName {
return binding
}
}
return nil
} }
type Binding struct { type Binding struct {
Field *reflect.StructField Field *reflect.StructField
FromNames []string FromNames []string
ToNames []string ToNames []string
ShouldOmitEmpty bool
Encoder ValEncoder Encoder ValEncoder
Decoder ValDecoder Decoder ValDecoder
} }
@ -131,47 +139,75 @@ func getTypeEncoderFromExtension(typ reflect.Type) ValEncoder {
} }
func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, error) { func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, error) {
bindings := map[string]*Binding{} bindings := []*Binding{}
for _, field := range listStructFields(typ) { for i := 0; i < typ.NumField(); i++ {
tagParts := strings.Split(field.Tag.Get("json"), ",") field := typ.Field(i)
fieldNames := calcFieldNames(field.Name, tagParts[0]) if field.Anonymous {
fieldCacheKey := fmt.Sprintf("%s/%s", typ.String(), field.Name) if field.Type.Kind() == reflect.Struct {
decoder := fieldDecoders[fieldCacheKey] structDescriptor, err := describeStruct(cfg, field.Type)
if decoder == nil && len(fieldNames) > 0 { if err != nil {
var err error return nil, err
decoder, err = decoderOfType(cfg, field.Type) }
if err != nil { for _, binding := range structDescriptor.Fields {
return nil, err bindings = append(bindings, binding)
}
} else if field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
structDescriptor, err := describeStruct(cfg, field.Type.Elem())
if err != nil {
return nil, err
}
for _, binding := range structDescriptor.Fields {
binding.Encoder = &optionalEncoder{binding.Encoder}
binding.Encoder = &structFieldEncoder{&field, binding.Encoder, false}
binding.Decoder = &optionalDecoder{field.Type, binding.Decoder}
binding.Decoder = &structFieldDecoder{&field, binding.Decoder}
bindings = append(bindings, binding)
}
} }
} } else {
encoder := fieldEncoders[fieldCacheKey] tagParts := strings.Split(field.Tag.Get("json"), ",")
if encoder == nil && len(fieldNames) > 0 { fieldNames := calcFieldNames(field.Name, tagParts[0])
var err error fieldCacheKey := fmt.Sprintf("%s/%s", typ.String(), field.Name)
encoder, err = encoderOfType(cfg, field.Type) decoder := fieldDecoders[fieldCacheKey]
if err != nil { if decoder == nil && len(fieldNames) > 0 {
return nil, err var err error
decoder, err = decoderOfType(cfg, field.Type)
if err != nil {
return nil, err
}
} }
// map is stored as pointer in the struct encoder := fieldEncoders[fieldCacheKey]
if field.Type.Kind() == reflect.Map { if encoder == nil && len(fieldNames) > 0 {
encoder = &optionalEncoder{encoder} var err error
encoder, err = encoderOfType(cfg, field.Type)
if err != nil {
return nil, err
}
// map is stored as pointer in the struct
if field.Type.Kind() == reflect.Map {
encoder = &optionalEncoder{encoder}
}
} }
} binding := &Binding{
binding := &Binding{ Field: &field,
Field: field, FromNames: fieldNames,
FromNames: fieldNames, ToNames: fieldNames,
ToNames: fieldNames, Decoder: decoder,
Decoder: decoder, Encoder: encoder,
Encoder: encoder,
}
for _, tagPart := range tagParts[1:] {
if tagPart == "omitempty" {
binding.ShouldOmitEmpty = true
} else if tagPart == "string" {
binding.Decoder = &stringModeDecoder{binding.Decoder}
binding.Encoder = &stringModeEncoder{binding.Encoder}
} }
shouldOmitEmpty := false
for _, tagPart := range tagParts[1:] {
if tagPart == "omitempty" {
shouldOmitEmpty = true
} else if tagPart == "string" {
binding.Decoder = &stringModeDecoder{binding.Decoder}
binding.Encoder = &stringModeEncoder{binding.Encoder}
}
}
binding.Decoder = &structFieldDecoder{&field, binding.Decoder}
binding.Encoder = &structFieldEncoder{&field, binding.Encoder, shouldOmitEmpty}
bindings = append(bindings, binding)
} }
bindings[field.Name] = binding
} }
structDescriptor := &StructDescriptor{ structDescriptor := &StructDescriptor{
Type: typ, Type: typ,
@ -185,14 +221,6 @@ func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, err
func listStructFields(typ reflect.Type) []*reflect.StructField { func listStructFields(typ reflect.Type) []*reflect.StructField {
fields := []*reflect.StructField{} fields := []*reflect.StructField{}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if field.Anonymous {
fields = append(fields, listStructFields(field.Type)...)
} else {
fields = append(fields, &field)
}
}
return fields return fields
} }

View File

@ -8,24 +8,20 @@ import (
) )
func encoderOfStruct(cfg *frozenConfig, typ reflect.Type) (ValEncoder, error) { func encoderOfStruct(cfg *frozenConfig, typ reflect.Type) (ValEncoder, error) {
structEncoder_ := &structEncoder{}
fields := map[string]*structFieldEncoder{} fields := map[string]*structFieldEncoder{}
structDescriptor, err := describeStruct(cfg, typ) structDescriptor, err := describeStruct(cfg, typ)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, binding := range structDescriptor.Fields { for _, binding := range structDescriptor.Fields {
for _, fieldName := range binding.ToNames { for _, toName := range binding.ToNames {
fields[fieldName] = &structFieldEncoder{binding.Field, fieldName, binding.Encoder, binding.ShouldOmitEmpty} fields[toName] = binding.Encoder.(*structFieldEncoder)
} }
} }
if len(fields) == 0 { if len(fields) == 0 {
return &emptyStructEncoder{}, nil return &emptyStructEncoder{}, nil
} }
for _, field := range fields { return &structEncoder{fields}, nil
structEncoder_.fields = append(structEncoder_.fields, field)
}
return structEncoder_, nil
} }
func decoderOfStruct(cfg *frozenConfig, typ reflect.Type) (ValDecoder, error) { func decoderOfStruct(cfg *frozenConfig, typ reflect.Type) (ValDecoder, error) {
@ -35,8 +31,8 @@ func decoderOfStruct(cfg *frozenConfig, typ reflect.Type) (ValDecoder, error) {
return nil, err return nil, err
} }
for _, binding := range structDescriptor.Fields { for _, binding := range structDescriptor.Fields {
for _, fieldName := range binding.FromNames { for _, fromName := range binding.FromNames {
fields[fieldName] = &structFieldDecoder{binding.Field, binding.Decoder} fields[fromName] = binding.Decoder.(*structFieldDecoder)
} }
} }
return createStructDecoder(typ, fields) return createStructDecoder(typ, fields)
@ -959,14 +955,12 @@ func (decoder *structFieldDecoder) decode(ptr unsafe.Pointer, iter *Iterator) {
type structFieldEncoder struct { type structFieldEncoder struct {
field *reflect.StructField field *reflect.StructField
fieldName string
fieldEncoder ValEncoder fieldEncoder ValEncoder
omitempty bool omitempty bool
} }
func (encoder *structFieldEncoder) encode(ptr unsafe.Pointer, stream *Stream) { func (encoder *structFieldEncoder) encode(ptr unsafe.Pointer, stream *Stream) {
fieldPtr := uintptr(ptr) + encoder.field.Offset fieldPtr := uintptr(ptr) + encoder.field.Offset
stream.WriteObjectField(encoder.fieldName)
encoder.fieldEncoder.encode(unsafe.Pointer(fieldPtr), stream) encoder.fieldEncoder.encode(unsafe.Pointer(fieldPtr), stream)
if stream.Error != nil && stream.Error != io.EOF { if stream.Error != nil && stream.Error != io.EOF {
stream.Error = fmt.Errorf("%s: %s", encoder.field.Name, stream.Error.Error()) stream.Error = fmt.Errorf("%s: %s", encoder.field.Name, stream.Error.Error())
@ -983,19 +977,20 @@ func (encoder *structFieldEncoder) isEmpty(ptr unsafe.Pointer) bool {
} }
type structEncoder struct { type structEncoder struct {
fields []*structFieldEncoder fields map[string]*structFieldEncoder
} }
func (encoder *structEncoder) encode(ptr unsafe.Pointer, stream *Stream) { func (encoder *structEncoder) encode(ptr unsafe.Pointer, stream *Stream) {
stream.WriteObjectStart() stream.WriteObjectStart()
isNotFirst := false isNotFirst := false
for _, field := range encoder.fields { for fieldName, field := range encoder.fields {
if field.omitempty && field.isEmpty(ptr) { if field.omitempty && field.isEmpty(ptr) {
continue continue
} }
if isNotFirst { if isNotFirst {
stream.WriteMore() stream.WriteMore()
} }
stream.WriteObjectField(fieldName)
field.encode(ptr, stream) field.encode(ptr, stream)
isNotFirst = true isNotFirst = true
} }
@ -1006,17 +1001,23 @@ func (encoder *structEncoder) encodeInterface(val interface{}, stream *Stream) {
var encoderToUse ValEncoder var encoderToUse ValEncoder
encoderToUse = encoder encoderToUse = encoder
if len(encoder.fields) == 1 { if len(encoder.fields) == 1 {
firstEncoder := encoder.fields[0].fieldEncoder var firstField *structFieldEncoder
var firstFieldName string
for fieldName, field := range encoder.fields {
firstFieldName = fieldName
firstField = field
}
firstEncoder := firstField.fieldEncoder
firstEncoderName := reflect.TypeOf(firstEncoder).String() firstEncoderName := reflect.TypeOf(firstEncoder).String()
// interface{} has inline optimization for this case // interface{} has inline optimization for this case
if firstEncoderName == "*jsoniter.optionalEncoder" { if firstEncoderName == "*jsoniter.optionalEncoder" {
encoderToUse = &structEncoder{ encoderToUse = &structEncoder{
fields: []*structFieldEncoder{{ fields: map[string]*structFieldEncoder{
field: encoder.fields[0].field, firstFieldName: {
fieldName: encoder.fields[0].fieldName, field: firstField.field,
fieldEncoder: firstEncoder.(*optionalEncoder).valueEncoder, fieldEncoder: firstEncoder.(*optionalEncoder).valueEncoder,
omitempty: encoder.fields[0].omitempty, omitempty: firstField.omitempty,
}}, }},
} }
} }
} }

View File

@ -93,7 +93,7 @@ func (extension *testExtension) UpdateStructDescriptor(structDescriptor *StructD
if structDescriptor.Type.String() != "jsoniter.TestObject1" { if structDescriptor.Type.String() != "jsoniter.TestObject1" {
return return
} }
binding := structDescriptor.Fields["field1"] binding := structDescriptor.GetField("field1")
binding.Encoder = &funcEncoder{fun: func(ptr unsafe.Pointer, stream *Stream) { binding.Encoder = &funcEncoder{fun: func(ptr unsafe.Pointer, stream *Stream) {
str := *((*string)(ptr)) str := *((*string)(ptr))
val, _ := strconv.Atoi(str) val, _ := strconv.Atoi(str)

View File

@ -323,6 +323,79 @@ func Test_decode_anonymous_struct(t *testing.T) {
should.Equal("value", outer.Key) should.Equal("value", outer.Key)
} }
func Test_multiple_level_anonymous_struct(t *testing.T) {
type Level1 struct {
Field1 string
}
type Level2 struct {
Level1
Field2 string
}
type Level3 struct {
Level2
Field3 string
}
should := require.New(t)
output, err := MarshalToString(Level3{Level2{Level1{"1"}, "2"}, "3"})
should.Nil(err)
should.Contains(output, `"Field1":"1"`)
should.Contains(output, `"Field2":"2"`)
should.Contains(output, `"Field3":"3"`)
}
func Test_multiple_level_anonymous_struct_with_ptr(t *testing.T) {
type Level1 struct {
Field1 string
Field2 string
Field4 string
}
type Level2 struct {
*Level1
Field2 string
Field3 string
}
type Level3 struct {
*Level2
Field3 string
}
should := require.New(t)
output, err := MarshalToString(Level3{&Level2{&Level1{"1", "", "4"}, "2", ""}, "3"})
should.Nil(err)
should.Contains(output, `"Field1":"1"`)
should.Contains(output, `"Field2":"2"`)
should.Contains(output, `"Field3":"3"`)
should.Contains(output, `"Field4":"4"`)
}
func Test_shadow_struct_field(t *testing.T) {
should := require.New(t)
type omit *struct{}
type CacheItem struct {
Key string `json:"key"`
MaxAge int `json:"cacheAge"`
}
output, err := MarshalToString(struct {
*CacheItem
// Omit bad keys
OmitMaxAge omit `json:"cacheAge,omitempty"`
// Add nice keys
MaxAge int `json:"max_age"`
}{
CacheItem: &CacheItem{
Key: "value",
MaxAge: 100,
},
MaxAge: 20,
})
should.Nil(err)
should.Contains(output, `"key":"value"`)
should.Contains(output, `"max_age":20`)
}
func Test_decode_nested(t *testing.T) { func Test_decode_nested(t *testing.T) {
type StructOfString struct { type StructOfString struct {
Field1 string Field1 string