mirror of
https://github.com/json-iterator/go.git
synced 2024-11-27 08:30:57 +02:00
#54 support sort map keys
This commit is contained in:
parent
e0e2423e9a
commit
69bc64b6d8
@ -12,26 +12,30 @@ type Config struct {
|
||||
IndentionStep int
|
||||
MarshalFloatWith6Digits bool
|
||||
SupportUnexportedStructFields bool
|
||||
EscapeHtml bool
|
||||
EscapeHtml bool
|
||||
SortMapKeys bool
|
||||
}
|
||||
|
||||
type frozenConfig struct {
|
||||
configBeforeFrozen Config
|
||||
indentionStep int
|
||||
decoderCache unsafe.Pointer
|
||||
encoderCache unsafe.Pointer
|
||||
extensions []ExtensionFunc
|
||||
configBeforeFrozen Config
|
||||
sortMapKeys bool
|
||||
indentionStep int
|
||||
decoderCache unsafe.Pointer
|
||||
encoderCache unsafe.Pointer
|
||||
extensions []ExtensionFunc
|
||||
}
|
||||
|
||||
var ConfigOfDefault = Config{}.Froze()
|
||||
|
||||
// Trying to be 100% compatible with standard library behavior
|
||||
var ConfigCompatibleWithStandardLibrary = Config{
|
||||
EscapeHtml: true,
|
||||
EscapeHtml: true,
|
||||
SortMapKeys: true,
|
||||
}.Froze()
|
||||
|
||||
func (cfg Config) Froze() *frozenConfig {
|
||||
frozenConfig := &frozenConfig{
|
||||
sortMapKeys: cfg.SortMapKeys,
|
||||
indentionStep: cfg.IndentionStep,
|
||||
}
|
||||
atomic.StorePointer(&frozenConfig.decoderCache, unsafe.Pointer(&map[string]Decoder{}))
|
||||
@ -232,4 +236,4 @@ func (cfg *frozenConfig) Unmarshal(data []byte, v interface{}) error {
|
||||
iter.reportError("Unmarshal", "there are bytes left after unmarshal")
|
||||
}
|
||||
return iter.Error
|
||||
}
|
||||
}
|
||||
|
@ -487,6 +487,9 @@ func encoderOfMap(cfg *frozenConfig, typ reflect.Type) (Encoder, error) {
|
||||
return nil, err
|
||||
}
|
||||
mapInterface := reflect.New(typ).Elem().Interface()
|
||||
encoderForMap := &mapEncoder{typ, elemType, encoder, *((*emptyInterface)(unsafe.Pointer(&mapInterface)))}
|
||||
return encoderForMap, nil
|
||||
if cfg.sortMapKeys {
|
||||
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"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type mapDecoder struct {
|
||||
@ -136,3 +137,81 @@ func (encoder *mapEncoder) isEmpty(ptr unsafe.Pointer) bool {
|
||||
realVal := reflect.ValueOf(*realInterface)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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…
Reference in New Issue
Block a user