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:
parent
e0e2423e9a
commit
69bc64b6d8
@ -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{}))
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user