大佬们求解一个 go map 无序的问题

2021-06-02 20:34:34 +08:00
 liyaojian

要求:需要根据用户传入的 jsonStr 中的nameuser_id的顺序拼接其值。

代码:

package main

import (
	"encoding/json"
	"fmt"
	"reflect"
)

func main() {
	jsonStr := `{"name":"tom","user_id":"123"}` // 这是传入的参数,name 与 user_id 顺序不能确定先后
	var str string
	m := make(map[string]interface{})
	_ = json.Unmarshal([]byte(jsonStr), &m)
	
	v := reflect.ValueOf(m)
	keys := v.MapKeys()
	for _, key := range keys {
		v1 := v.MapIndex(key).Interface().(string)
		str += v1
	}
	fmt.Println(str)
	// 由于 map 无序,不能固定输出:tom123
	// 如何保持与 json 中键一致,固定输出?
	// 比如若 json_str := `{"user_id":"123""name":"tom"}` 则输出 123tom
}

在线运行: https://play.golang.org/p/_ZMfsISpKWz

还请大佬们赐教,感激不尽。

3082 次点击
所在节点    Go 编程语言
30 条回复
wjfz
2021-06-02 22:56:55 +08:00
我这还有个骚操作,重新 marshal 一遍在 unMashal 出来就有序了。

```
package main

import (
"encoding/json"
"fmt"
"reflect"
)

func main() {
jsonStr := `{"name":"tom","user_id":"123"}` // 这是传入的参数,name 与 user_id 顺序不能确定先后
var str string
m := make(map[string]interface{})
_ = json.Unmarshal([]byte(jsonStr), &m)

json1,_ := json.Marshal(m)
_ = json.Unmarshal([]byte(json1), &m)

v := reflect.ValueOf(m)
keys := v.MapKeys()
for _, key := range keys {
v1 := v.MapIndex(key).Interface().(string)
str += v1
}
fmt.Println(str)
// 由于 map 无序,不能固定输出:tom123
// 如何保持与 json 中键一致,固定输出?
// 比如若 json_str := `{"user_id":"123""name":"tom"}` 则输出 123tom
}

```
wjfz
2021-06-02 22:57:30 +08:00
如果是为了做签名,直接把 json 字符串拿去加密也是一种可选项。
labulaka521
2021-06-03 08:30:16 +08:00
像淘宝 pdd 的一些开放接口 key 的顺序都是字符序,
Muninn
2021-06-03 09:04:12 +08:00
The JSON Data Interchange Standard definition at json.org specifies that “An object is an unordered [emphasis mine] set of name/value pairs”, whereas an array is an “ordered collection of values”. In other words, by definition the order of the key/value pairs within JSON objects simply does not, and should not, matter.

这跟 map struct 根本没关系。因为 json 就是无序的,官方说你要有序你就用 array 。

楼主相当于要解析一个看起来像 json 但是不是 json 的东西,那只能自己解析了……
只要是任何一个语言,用 json 库就是无序的。就算是有序的,也是实现的时候不小心有序了,将来随时可能无序。
bwangel
2021-06-03 14:00:33 +08:00
如果这是个语言问题,我比较赞同 8 楼的做法,gjson foreach 输的 json key,value 对是按照解析顺序输出的,比较满足你的需求。

如果这是个工程问题,我不建议使用 "github.com/tidwall/gjson",因为这样写了之后让代码更加晦涩难懂了,不利于维护。

在 json.encoder 一方看来,调整 json 中 map 的顺序完全不会有什么影响,因为这样做是符合 json 规范的,但是一调整就挂了。解决方案就是需要在代码中写个注释,说明顺序千万千万不能改,但是我们都知道,注释是及其不靠谱的,很多时候代码和注释完全对不上。

所以我的建议是

在 json 数据中加一个 order 字段,表明期望得到的顺序,这是一个示例

https://play.golang.org/p/c2DIY3q_vjR
GTim
2021-06-03 19:27:38 +08:00
package main

import (
"encoding/json"
"fmt"
"reflect"
"sort"
)

func main() {
jsonStr := `{"name":"tom","user_id":"123"}`
var str string
m := make(map[string]interface{})
_ = json.Unmarshal([]byte(jsonStr), &m)

v := reflect.ValueOf(m)
keys := make([]string, 0)
keysMap := map[string]reflect.Value{}
for _, key := range v.MapKeys() {
keys = append(keys, key.String())
keysMap[key.String()] = key
}

sort.Strings(keys)

for _, key := range keys {
v1 := v.MapIndex(keysMap[key]).Interface().(string)
str += v1
}
fmt.Println(str)
}
admpubcom
2021-06-04 01:52:36 +08:00
@GTim 直接用 for m 就行了干嘛还用反射?
AlexSLQ
2021-06-08 10:04:56 +08:00
为什么不用 struct.name+struct.userID 或者 map["name"]+map["userID"]这种,都确定就用这两种字段了就明着用呗
HUNYXV
2021-06-11 10:49:27 +08:00
https://play.golang.org/p/iyNvEWlS696
使用 struct 就好 然后实现 String() 接口

```go
type User struct {
Name string `json:"name"`
UserID string `json:"user_id"`
}

func (u *User) String() string {
return fmt.Sprintf("%s%s", u.Name, u.UserID)
}

func main() {
jsonStr := `{"name":"tom","user_id":"123"}` // 这是传入的参数,name 与 user_id 顺序不能确定先后
var user *User
_ = json.Unmarshal([]byte(jsonStr), &user)


fmt.Println(user)
}
```
chenall
2021-07-10 08:51:16 +08:00
建行的聚合支付接口。
就是使用楼主这种逻辑进行签名。

我是直接提前把 key 的顺序做成一个有序列表。
然后再遍历。
只是这样子,后面有要扩展增加字段的时候,就要重新再修改这个列表。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/780962

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX