Jelajahi Sumber

#54 support sort map keys

Tao Wen 8 tahun lalu
induk
melakukan
69bc64b6d8
4 mengubah file dengan 110 tambahan dan 10 penghapusan
  1. 12 8
      feature_config.go
  2. 5 2
      feature_reflect.go
  3. 79 0
      feature_reflect_map.go
  4. 14 0
      jsoniter_map_test.go

+ 12 - 8
feature_config.go

@@ -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
-}
+}

+ 5 - 2
feature_reflect.go

@@ -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
+	}
 }

+ 79 - 0
feature_reflect_map.go

@@ -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
+}

+ 14 - 0
jsoniter_map_test.go

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