1
0
mirror of https://github.com/json-iterator/go.git synced 2025-06-06 22:36:25 +02:00

#54 support sort map keys

This commit is contained in:
Tao Wen 2017-06-16 16:46:30 +08:00
parent e0e2423e9a
commit 69bc64b6d8
4 changed files with 110 additions and 10 deletions

View File

@ -13,10 +13,12 @@ type Config struct {
MarshalFloatWith6Digits bool MarshalFloatWith6Digits bool
SupportUnexportedStructFields bool SupportUnexportedStructFields bool
EscapeHtml bool EscapeHtml bool
SortMapKeys bool
} }
type frozenConfig struct { type frozenConfig struct {
configBeforeFrozen Config configBeforeFrozen Config
sortMapKeys bool
indentionStep int indentionStep int
decoderCache unsafe.Pointer decoderCache unsafe.Pointer
encoderCache unsafe.Pointer encoderCache unsafe.Pointer
@ -28,10 +30,12 @@ var ConfigOfDefault = Config{}.Froze()
// Trying to be 100% compatible with standard library behavior // Trying to be 100% compatible with standard library behavior
var ConfigCompatibleWithStandardLibrary = Config{ var ConfigCompatibleWithStandardLibrary = Config{
EscapeHtml: true, EscapeHtml: true,
SortMapKeys: true,
}.Froze() }.Froze()
func (cfg Config) Froze() *frozenConfig { func (cfg Config) Froze() *frozenConfig {
frozenConfig := &frozenConfig{ frozenConfig := &frozenConfig{
sortMapKeys: cfg.SortMapKeys,
indentionStep: cfg.IndentionStep, indentionStep: cfg.IndentionStep,
} }
atomic.StorePointer(&frozenConfig.decoderCache, unsafe.Pointer(&map[string]Decoder{})) atomic.StorePointer(&frozenConfig.decoderCache, unsafe.Pointer(&map[string]Decoder{}))

View File

@ -487,6 +487,9 @@ func encoderOfMap(cfg *frozenConfig, typ reflect.Type) (Encoder, error) {
return nil, err return nil, err
} }
mapInterface := reflect.New(typ).Elem().Interface() mapInterface := reflect.New(typ).Elem().Interface()
encoderForMap := &mapEncoder{typ, elemType, encoder, *((*emptyInterface)(unsafe.Pointer(&mapInterface)))} if cfg.sortMapKeys {
return encoderForMap, nil return &sortKeysMapEncoder{typ, elemType, encoder, *((*emptyInterface)(unsafe.Pointer(&mapInterface)))}, nil
} else {
return &mapEncoder{typ, elemType, encoder, *((*emptyInterface)(unsafe.Pointer(&mapInterface)))}, nil
}
} }

View File

@ -6,6 +6,7 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"unsafe" "unsafe"
"sort"
) )
type mapDecoder struct { type mapDecoder struct {
@ -136,3 +137,81 @@ func (encoder *mapEncoder) isEmpty(ptr unsafe.Pointer) bool {
realVal := reflect.ValueOf(*realInterface) realVal := reflect.ValueOf(*realInterface)
return realVal.Len() == 0 return realVal.Len() == 0
} }
type sortKeysMapEncoder struct {
mapType reflect.Type
elemType reflect.Type
elemEncoder Encoder
mapInterface emptyInterface
}
func (encoder *sortKeysMapEncoder) encode(ptr unsafe.Pointer, stream *Stream) {
mapInterface := encoder.mapInterface
mapInterface.word = ptr
realInterface := (*interface{})(unsafe.Pointer(&mapInterface))
realVal := reflect.ValueOf(*realInterface)
// Extract and sort the keys.
keys := realVal.MapKeys()
sv := make([]reflectWithString, len(keys))
for i, v := range keys {
sv[i].v = v
if err := sv[i].resolve(); err != nil {
stream.Error = err
return
}
}
sort.Slice(sv, func(i, j int) bool { return sv[i].s < sv[j].s })
stream.WriteObjectStart()
for i, key := range sv {
if i != 0 {
stream.WriteMore()
}
encodeMapKey(key.v, stream)
stream.writeByte(':')
val := realVal.MapIndex(key.v).Interface()
encoder.elemEncoder.encodeInterface(val, stream)
}
stream.WriteObjectEnd()
}
type reflectWithString struct {
v reflect.Value
s string
}
func (w *reflectWithString) resolve() error {
if w.v.Kind() == reflect.String {
w.s = w.v.String()
return nil
}
if tm, ok := w.v.Interface().(encoding.TextMarshaler); ok {
buf, err := tm.MarshalText()
w.s = string(buf)
return err
}
switch w.v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
w.s = strconv.FormatInt(w.v.Int(), 10)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
w.s = strconv.FormatUint(w.v.Uint(), 10)
return nil
}
panic("unexpected map key type")
}
func (encoder *sortKeysMapEncoder) encodeInterface(val interface{}, stream *Stream) {
writeToStream(val, stream, encoder)
}
func (encoder *sortKeysMapEncoder) isEmpty(ptr unsafe.Pointer) bool {
mapInterface := encoder.mapInterface
mapInterface.word = ptr
realInterface := (*interface{})(unsafe.Pointer(&mapInterface))
realVal := reflect.ValueOf(*realInterface)
return realVal.Len() == 0
}

View File

@ -125,3 +125,17 @@ func Test_map_key_with_escaped_char(t *testing.T) {
should.Equal(map[string]string{"k\"ey": "val"}, obj.Map) should.Equal(map[string]string{"k\"ey": "val"}, obj.Map)
} }
} }
func Test_encode_map_with_sorted_keys(t *testing.T) {
should := require.New(t)
m := map[string]interface{}{
"3": 3,
"1": 1,
"2": 2,
}
bytes, err := json.Marshal(m)
should.Nil(err)
output, err := ConfigCompatibleWithStandardLibrary.MarshalToString(m)
should.Nil(err)
should.Equal(string(bytes), output)
}