mirror of
https://github.com/json-iterator/go.git
synced 2025-03-20 20:54:55 +02:00
#63 keep struct field order
This commit is contained in:
parent
d7ea1acd3f
commit
8f8e16b4c2
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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++
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user