diff --git a/feature_config.go b/feature_config.go index 217c956..430ea00 100644 --- a/feature_config.go +++ b/feature_config.go @@ -49,7 +49,7 @@ var ConfigCompatibleWithStandardLibrary = Config{ }.Froze() var ConfigFastest = Config{ - EscapeHtml: false, + EscapeHtml: false, MarshalFloatWith6Digits: true, }.Froze() @@ -144,7 +144,6 @@ func (encoder *htmlEscapedStringEncoder) IsEmpty(ptr unsafe.Pointer) bool { } func (cfg *frozenConfig) escapeHtml() { - // for better performance cfg.addEncoderToCache(reflect.TypeOf((*string)(nil)).Elem(), &htmlEscapedStringEncoder{}) } @@ -192,14 +191,14 @@ func (cfg *frozenConfig) getEncoderFromCache(cacheKey reflect.Type) ValEncoder { func (cfg *frozenConfig) cleanDecoders() { typeDecoders = map[string]ValDecoder{} fieldDecoders = map[string]ValDecoder{} - atomic.StorePointer(&cfg.decoderCache, unsafe.Pointer(&map[string]ValDecoder{})) + *cfg = *cfg.configBeforeFrozen.Froze() } // cleanEncoders cleans encoders registered or cached func (cfg *frozenConfig) cleanEncoders() { typeEncoders = map[string]ValEncoder{} fieldEncoders = map[string]ValEncoder{} - atomic.StorePointer(&cfg.encoderCache, unsafe.Pointer(&map[string]ValEncoder{})) + *cfg = *cfg.configBeforeFrozen.Froze() } func (cfg *frozenConfig) MarshalToString(v interface{}) (string, error) { diff --git a/feature_reflect_extension.go b/feature_reflect_extension.go index fe705b3..4babc42 100644 --- a/feature_reflect_extension.go +++ b/feature_reflect_extension.go @@ -192,7 +192,8 @@ func _getTypeEncoderFromExtension(typ reflect.Type) ValEncoder { } func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, error) { - anonymousBindings := []*Binding{} + headAnonymousBindings := []*Binding{} + tailAnonymousBindings := []*Binding{} bindings := []*Binding{} for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) @@ -205,7 +206,11 @@ func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, err for _, binding := range structDescriptor.Fields { binding.Encoder = &structFieldEncoder{&field, binding.Encoder, false} binding.Decoder = &structFieldDecoder{&field, binding.Decoder} - anonymousBindings = append(anonymousBindings, binding) + if field.Offset == 0 { + headAnonymousBindings = append(headAnonymousBindings, binding) + } else { + tailAnonymousBindings = append(tailAnonymousBindings, binding) + } } } else if field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct { structDescriptor, err := describeStruct(cfg, field.Type.Elem()) @@ -217,7 +222,11 @@ func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, err binding.Encoder = &structFieldEncoder{&field, binding.Encoder, false} binding.Decoder = &optionalDecoder{field.Type, binding.Decoder} binding.Decoder = &structFieldDecoder{&field, binding.Decoder} - anonymousBindings = append(anonymousBindings, binding) + if field.Offset == 0 { + headAnonymousBindings = append(headAnonymousBindings, binding) + } else { + tailAnonymousBindings = append(tailAnonymousBindings, binding) + } } } } else { @@ -276,7 +285,8 @@ func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, err binding.Encoder = &structFieldEncoder{binding.Field, binding.Encoder, shouldOmitEmpty} } // insert anonymous bindings to the head - structDescriptor.Fields = append(anonymousBindings, structDescriptor.Fields...) + structDescriptor.Fields = append(headAnonymousBindings, structDescriptor.Fields...) + structDescriptor.Fields = append(structDescriptor.Fields, tailAnonymousBindings...) return structDescriptor, nil } diff --git a/feature_reflect_object.go b/feature_reflect_object.go index d0b7191..ea31b33 100644 --- a/feature_reflect_object.go +++ b/feature_reflect_object.go @@ -8,20 +8,35 @@ import ( ) func encoderOfStruct(cfg *frozenConfig, typ reflect.Type) (ValEncoder, error) { - fields := map[string]*structFieldEncoder{} + fieldsByToName := map[string]int{} + orderedFields := []*structFieldTo{} structDescriptor, err := describeStruct(cfg, typ) if err != nil { return nil, err } - for _, binding := range structDescriptor.Fields { + for index, binding := range structDescriptor.Fields { for _, toName := range binding.ToNames { - fields[toName] = binding.Encoder.(*structFieldEncoder) + oldIndex, found := fieldsByToName[toName] + if found { + orderedFields[oldIndex] = nil // replaced by the later one + } + fieldsByToName[toName] = index + orderedFields = append(orderedFields, &structFieldTo{ + binding.Encoder.(*structFieldEncoder), + toName, + }) } } - if len(fields) == 0 { + if len(orderedFields) == 0 { return &emptyStructEncoder{}, nil } - return &structEncoder{fields}, nil + finalOrderedFields := []structFieldTo{} + for _, structFieldTo := range orderedFields { + if structFieldTo != nil { + finalOrderedFields = append(finalOrderedFields, *structFieldTo) + } + } + return &structEncoder{finalOrderedFields}, nil } func decoderOfStruct(cfg *frozenConfig, typ reflect.Type) (ValDecoder, error) { @@ -977,21 +992,26 @@ func (encoder *structFieldEncoder) IsEmpty(ptr unsafe.Pointer) bool { } type structEncoder struct { - fields map[string]*structFieldEncoder + fields []structFieldTo +} + +type structFieldTo struct { + encoder *structFieldEncoder + toName string } func (encoder *structEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { stream.WriteObjectStart() isNotFirst := false - for fieldName, field := range encoder.fields { - if field.omitempty && field.IsEmpty(ptr) { + for _, field := range encoder.fields { + if field.encoder.omitempty && field.encoder.IsEmpty(ptr) { continue } if isNotFirst { stream.WriteMore() } - stream.WriteObjectField(fieldName) - field.Encode(ptr, stream) + stream.WriteObjectField(field.toName) + field.encoder.Encode(ptr, stream) isNotFirst = true } stream.WriteObjectEnd() @@ -1001,23 +1021,23 @@ func (encoder *structEncoder) EncodeInterface(val interface{}, stream *Stream) { var encoderToUse ValEncoder encoderToUse = encoder if len(encoder.fields) == 1 { - var firstField *structFieldEncoder - var firstFieldName string - for fieldName, field := range encoder.fields { - firstFieldName = fieldName - firstField = field - } + firstFieldName := encoder.fields[0].toName + firstField := encoder.fields[0].encoder firstEncoder := firstField.fieldEncoder firstEncoderName := reflect.TypeOf(firstEncoder).String() // interface{} has inline optimization for this case if firstEncoderName == "*jsoniter.optionalEncoder" { encoderToUse = &structEncoder{ - fields: map[string]*structFieldEncoder{ - firstFieldName: { - field: firstField.field, - fieldEncoder: firstEncoder.(*optionalEncoder).valueEncoder, - omitempty: firstField.omitempty, - }}, + fields: []structFieldTo{ + { + toName: firstFieldName, + encoder: &structFieldEncoder{ + field: firstField.field, + fieldEncoder: firstEncoder.(*optionalEncoder).valueEncoder, + omitempty: firstField.omitempty, + }, + }, + }, } } } @@ -1026,7 +1046,7 @@ func (encoder *structEncoder) EncodeInterface(val interface{}, stream *Stream) { func (encoder *structEncoder) IsEmpty(ptr unsafe.Pointer) bool { for _, field := range encoder.fields { - if !field.IsEmpty(ptr) { + if !field.encoder.IsEmpty(ptr) { return false } } diff --git a/feature_stream_string.go b/feature_stream_string.go index fdd8b9e..24ffec2 100644 --- a/feature_stream_string.go +++ b/feature_stream_string.go @@ -252,7 +252,7 @@ func (stream *Stream) WriteStringWithHtmlEscaped(s string) { func writeStringSlowPathWithHtmlEscaped(stream *Stream, i int, s string, valLen int) { start := i // for the remaining parts, we process them char by char - for ; i < valLen; { + for i < valLen { if b := s[i]; b < utf8.RuneSelf { if htmlSafeSet[b] { i++ @@ -351,7 +351,7 @@ func (stream *Stream) WriteString(s string) { func writeStringSlowPath(stream *Stream, i int, s string, valLen int) { start := i // for the remaining parts, we process them char by char - for ; i < valLen; { + for i < valLen { if b := s[i]; b < utf8.RuneSelf { if safeSet[b] { i++ diff --git a/jsoniter_object_test.go b/jsoniter_object_test.go index c484499..4ff9473 100644 --- a/jsoniter_object_test.go +++ b/jsoniter_object_test.go @@ -349,10 +349,7 @@ func Test_multiple_level_anonymous_struct(t *testing.T) { obj := Level3{Level2{Level1{"1"}, "2"}, "3"} output, err := MarshalToString(obj) should.Nil(err) - fmt.Println(output) - should.Contains(output, `"Field1":"1"`) - should.Contains(output, `"Field2":"2"`) - should.Contains(output, `"Field3":"3"`) + should.Equal(`{"Field1":"1","Field2":"2","Field3":"3"}`, output) } func Test_multiple_level_anonymous_struct_with_ptr(t *testing.T) { @@ -413,7 +410,7 @@ func Test_embed_at_last(t *testing.T) { } type Struct struct { - Field string `json:"field"` + Field string `json:"field"` FieldType string `json:"field_type"` Base } @@ -421,9 +418,7 @@ func Test_embed_at_last(t *testing.T) { s := Struct{Field: "field", FieldType: "field_type", Base: Base{"type"}} output, err := MarshalToString(s) should.Nil(err) - should.Contains(output, `"type":"type"`) - should.Contains(output, `"field":"field"`) - should.Contains(output, `"field_type":"field_type"`) + should.Equal(`{"field":"field","field_type":"field_type","type":"type"}`, output) } func Test_decode_nested(t *testing.T) { diff --git a/jsoniter_string_test.go b/jsoniter_string_test.go index aa398f7..f850d7f 100644 --- a/jsoniter_string_test.go +++ b/jsoniter_string_test.go @@ -138,28 +138,29 @@ func Test_string_encode_with_std_without_html_escape(t *testing.T) { func Test_unicode(t *testing.T) { should := require.New(t) - output , _ := MarshalToString(map[string]interface{}{"a": "数字山谷"}) + output, _ := MarshalToString(map[string]interface{}{"a": "数字山谷"}) should.Equal(`{"a":"数字山谷"}`, output) - output , _ = Config{EscapeHtml: false}.Froze().MarshalToString(map[string]interface{}{"a": "数字山谷"}) + output, _ = Config{EscapeHtml: false}.Froze().MarshalToString(map[string]interface{}{"a": "数字山谷"}) should.Equal(`{"a":"数字山谷"}`, output) } func Test_unicode_and_escape(t *testing.T) { should := require.New(t) - output , err := MarshalToString(`"数字山谷"`) + output, err := MarshalToString(`"数字山谷"`) should.Nil(err) should.Equal(`"\"数字山谷\""`, output) - output , err = ConfigFastest.MarshalToString(`"数字山谷"`) + output, err = ConfigFastest.MarshalToString(`"数字山谷"`) should.Nil(err) should.Equal(`"\"数字山谷\""`, output) } func Test_unsafe_unicode(t *testing.T) { + ConfigDefault.cleanEncoders() should := require.New(t) - output , err := MarshalToString("he\u2029\u2028he") + output, err := ConfigDefault.MarshalToString("he\u2029\u2028he") should.Nil(err) should.Equal(`"he\u2029\u2028he"`, output) - output , err = ConfigFastest.MarshalToString("he\u2029\u2028he") + output, err = ConfigFastest.MarshalToString("he\u2029\u2028he") should.Nil(err) should.Equal("\"he\u2029\u2028he\"", output) }