Skip to content

Commit 24596c5

Browse files
committed
Sort map keys before converting from Value to Json #21.
1 parent e7c6aa1 commit 24596c5

File tree

6 files changed

+68
-8
lines changed

6 files changed

+68
-8
lines changed

cbor.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ var hdrIndefiniteText = cborHdr(cborType3, cborIndefiniteLength)
122122
var hdrIndefiniteArray = cborHdr(cborType4, cborIndefiniteLength)
123123
var hdrIndefiniteMap = cborHdr(cborType5, cborIndefiniteLength)
124124

125-
// Cbor encapsulates configuration and a cbor buffer.
125+
// Cbor encapsulates configuration and a cbor buffer. Map element
126+
// in cbor encoding should have its keys sorted.
126127
type Cbor struct {
127128
config *Config
128129
data []byte

config_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,36 @@ func TestContainerEncodingString(t *testing.T) {
163163
}
164164
}
165165

166+
func TestCheckSortedkeys(t *testing.T) {
167+
val := map[string]interface{}{
168+
"ddd": true, "ccc": false, "fff": true, "aaa": nil,
169+
"bbb": []interface{}{1, 2},
170+
}
171+
srtjson := `{"aaa":null,"bbb":[1,2],"ccc":false,"ddd":true,"fff":true}`
172+
173+
config := NewDefaultConfig()
174+
jsn := config.NewJson(make([]byte, 1024), 0)
175+
cbr := config.NewCbor(make([]byte, 1024), 0)
176+
col := config.NewCollate(make([]byte, 1024), 0)
177+
178+
json := config.NewValue(val).Tojson(jsn.Reset(nil)).Bytes()
179+
if string(json) != srtjson {
180+
t.Errorf("expected %v, got %v", srtjson, string(json))
181+
}
182+
183+
cbr = config.NewValue(val).Tocbor(cbr.Reset(nil))
184+
json = cbr.Tojson(jsn.Reset(nil)).Bytes()
185+
if string(json) != srtjson {
186+
t.Errorf("expected %v, got %v", srtjson, string(json))
187+
}
188+
189+
col = config.NewValue(val).Tocollate(col.Reset(nil))
190+
json = col.Tocbor(cbr.Reset(nil)).Tojson(jsn.Reset(nil)).Bytes()
191+
if string(json) != srtjson {
192+
t.Errorf("expected %v, got %v", srtjson, string(json))
193+
}
194+
}
195+
166196
func testdataFile(filename string) []byte {
167197
f, err := os.Open(filename)
168198
if err != nil {

util.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -290,15 +290,29 @@ func collated2Json(code []byte, text []byte, nk NumberKind) int {
290290
}
291291

292292
// sort JSON property objects based on property names.
293-
func sortProps(props map[string]interface{}, keys []string) []string {
293+
func sortProps1(props map[string]interface{}, keys []string) []string {
294294
for k := range props {
295295
keys = append(keys, k)
296296
}
297297

298298
return sortStrings(keys)
299299
}
300300

301-
// bubble sort, moving to qsort should be atleast 40% faster.
301+
func sortProps2(props map[string]uint64, keys []string) []string {
302+
for k := range props {
303+
keys = append(keys, k)
304+
}
305+
return sortStrings(keys)
306+
}
307+
308+
func sortProps3(props [][2]interface{}, keys []string) []string {
309+
for _, item := range props {
310+
keys = append(keys, item[0].(string))
311+
}
312+
return sortStrings(keys)
313+
}
314+
315+
// TODO: bubble sort, moving to qsort should be atleast 40% faster.
302316
func sortStrings(strs []string) []string {
303317
for ln := len(strs) - 1; ; ln-- {
304318
changed := false

value_cbor.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,22 @@ func value2cbor(item interface{}, out []byte, config *Config) int {
7070
case [][2]interface{}:
7171
n += valmap2cbor(v, out, config)
7272
case map[string]interface{}:
73+
poolobj := config.pools.keysPool.Get()
74+
keys := poolobj.([]string)
75+
defer config.pools.keysPool.Put(poolobj)
76+
7377
if config.ct == LengthPrefix {
7478
n += valuint642cbor(uint64(len(v)), out[n:])
7579
out[n-1] = (out[n-1] & 0x1f) | cborType5 // fix the type
76-
for key, value := range v {
80+
for _, key := range sortProps1(v, keys) {
81+
value := v[key]
7782
n += valtext2cbor(key, out[n:])
7883
n += value2cbor(value, out[n:], config)
7984
}
8085
} else if config.ct == Stream {
8186
n += mapStart(out[n:])
82-
for key, value := range v {
87+
for _, key := range sortProps1(v, keys) {
88+
value := v[key]
8389
n += valtext2cbor(key, out[n:])
8490
n += value2cbor(value, out[n:], config)
8591
}

value_collate.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ func gson2collate(obj interface{}, code []byte, config *Config) int {
133133
poolobj := config.pools.keysPool.Get()
134134
keys := poolobj.([]string)
135135
defer config.pools.keysPool.Put(poolobj)
136-
for _, key := range sortProps(value, keys) {
136+
137+
for _, key := range sortProps1(value, keys) {
137138
n += collateString(key, code[n:], config) // encode key
138139
n += gson2collate(value[key], code[n:], config) // encode value
139140
}

value_json.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,12 @@ func value2json(value interface{}, out []byte, config *Config) int {
114114
out[n] = '{'
115115
n++
116116

117+
poolobj := config.pools.keysPool.Get()
118+
keys := poolobj.([]string)
119+
defer config.pools.keysPool.Put(poolobj)
120+
117121
count := len(v)
118-
for key := range v {
122+
for _, key := range sortProps1(v, keys) {
119123
outsl, err = encodeString(str2bytes(key), out[n:n])
120124
if err != nil {
121125
panic("error encoding key")
@@ -141,8 +145,12 @@ func value2json(value interface{}, out []byte, config *Config) int {
141145
out[n] = '{'
142146
n++
143147

148+
poolobj := config.pools.keysPool.Get()
149+
keys := poolobj.([]string)
150+
defer config.pools.keysPool.Put(poolobj)
151+
144152
count := len(v)
145-
for key := range v {
153+
for _, key := range sortProps2(v, keys) {
146154
outsl, err = encodeString(str2bytes(key), out[n:n])
147155
if err != nil {
148156
panic("error encoding key")

0 commit comments

Comments
 (0)