Browse Source

#63 keep struct field order

Tao Wen 8 years ago
parent
commit
8f8e16b4c2

+ 3 - 4
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) {

+ 14 - 4
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
 }
 

+ 43 - 23
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
 		}
 	}

+ 2 - 2
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++

+ 3 - 8
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) {

+ 7 - 6
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)
 }