大佬们求解一个 go map 无序的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
liyaojian
V2EX    Go 编程语言

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

  •  
  •   liyaojian 2021-06-02 20:34:34 +08:00 3606 次点击
    这是一个创建于 1593 天前的主题,其中的信息可能已经有所发展或是发生改变。

    要求:需要根据用户传入的 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

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

    第 1 条附言    2021-06-02 22:36:13 +08:00
    非常感谢各位献计! 8 楼的方案可以解决我的问题。可能是表述不准确,应该是:由于 map 的无序,想求个其他方案解题。

    关于做啥?是用在数据签名这块的,有历史遗留问题,如果现在重新设计,我也不会考虑这种奇葩的拼接方式。

    再次感谢各位大佬。
    30 条回复    2021-07-10 08:51:16 +08:00
    Jason0803
        1
    Jason0803  
       2021-06-02 20:46:37 +08:00
    为什么不用 struct
    sunny352787
        2
    sunny352787  
       2021-06-02 20:48:07 +08:00
    确定参数就用 struct,不确定就全取出来塞到 slice 里排序再用
    hello2060
        3
    hello2060  
       2021-06-02 20:51:19 +08:00
    不懂 go, 但是你既然只能转到 map, map 里的 key 不是按照输入顺序排序, 那 map 是没法解决这问题的.
    liyaojian
        4
    liyaojian  
    OP
       2021-06-02 20:54:03 +08:00
    @Jason0803 #1
    @sunny352787 #2
    struct 也不能确定用户传过来的 name 和 user_id 的顺序啊,我需要获取 name 和 user_id 对应顺序的值拼接字符串,不同的顺序拼接的字符串不一样
    liyaojian
        5
    liyaojian  
    OP
       2021-06-02 20:55:52 +08:00
    @hello2060 #3 所以问下论坛里的大佬有没有其他方法解决这个问题,jsonStr 是用户的传参,这个不能变
    virusdefender
        6
    virusdefender  
       2021-06-02 20:57:48 +08:00
    如果只有这么两个字段的话,正则处理下就好了
    lujjjh
        7
    lujjjh  
       2021-06-02 21:01:58 +08:00   1
    很奇怪的需求,但也是有办法的: https://play.golang.org/p/PAYLlXZjhgF
    Kisesy
        8
    Kisesy  
       2021-06-02 21:11:04 +08:00   1
    sphawkcn
        9
    sphawkcn  
       2021-06-02 21:19:41 +08:00
    @lujjjh #7 在以前的 AAuto (现在叫 aardio )的圈子里,有个人跟你一样叫 lujjjh,请问是你吗?我的朋友
    xiaoyiyu
        10
    xiaoyiyu  
       2021-06-02 21:26:27 +08:00   1
    struct + String() 不就可以了 https://play.golang.org/p/V6RXkf-sceA
    hello2060
        11
    hello2060  
       2021-06-02 21:30:35 +08:00
    @liyaojian 如果只要能 work 就行, 你搜整个 string 看那个在前哪个在后就可以了, 搜"name":和"user_id":
    hello2060
        12
    hello2060  
       2021-06-02 21:35:07 +08:00   1
    我不懂 go, 随便搜了下, 问题也没仔细看 https://play.golang.org/p/yZ5DxZLIMXC 这个是不是可以你看看
    rekulas
        13
    rekulas  
       2021-06-02 21:52:42 +08:00   1
    很奇怪的需求 用第三方库可以容易的实现 采用类似其他语言的 loop 对象的 key 即可
    ```
    package main

    import (
    "github.com/tidwall/gjson"
    "log"
    )

    type loop func(key gjson.Result,value gjson.Result)

    func main() {
    jsonStr := `{"name":"tom","user_id":"123"}`
    expectedResult := ""
    result := gjson.Parse(jsonStr)
    result.ForEach(func(key, value gjson.Result) bool {
    expectedResult += value.String()
    return true
    })
    log.Println(expectedResult)
    }
    ```

    不过我敢肯定,这个功能的设计绝对是有问题的。。
    rekulas
        14
    rekulas  
       2021-06-02 21:56:18 +08:00
    才发现跟#8 的重复了
    SorcererXW
        15
    SorcererXW  
       2021-06-02 21:57:51 +08:00   1
    直接用正则表达式把所有 key 提取出来就能知道顺序了
    https://stackoverflow.com/questions/24300112/regex-to-match-keys-in-json
    CEBBCAT
        16
    CEBBCAT  
       2021-06-02 22:02:01 +08:00   1
    其实按照规范来说,JSON 的键值对是无序的[1],可以主张更换数据结构来规避这个“需要 JSON 的键按序解析”的问题。但假如这个用户比较顶……

    再回到这个问题,map 的遍历在 Go 标准实现中也是无序的,所以楼主你这是在做无用功,或者说危险功。

    我能想到的是定义一个 interface,实现一个 String()方法。然后在它的 UnmarshalJSON()中根据`"name"`和`"user_id"`的先后出现位置,来返回不同的 interface 。


    1. https://stackoverflow.com/a/16870531
    CEBBCAT
        17
    CEBBCAT  
       2021-06-02 22:03:27 +08:00
    话说回来,我在这里祈祷楼主知道“XY 问题”这个名词,不要等到最后才说出真实的需求……
    lujjjh
        18
    lujjjh  
       2021-06-02 22:19:21 +08:00
    @sphawkcn 是我

    @CEBBCAT 认同你的观点,既然规范里明确说了是无序的,就**不应该**依赖某种语言 /某种库下有序的特定实现。

    Go 1 开始刻意把迭代 map 的顺序设计成随机,也是为了防止程序员依赖某个 Go 版本实现下的迭代顺序,而不同实现的迭代顺序是有可能不同的,就会造成可移植性的问题,索性设计成随机了。

    顺便分享一个近期的故事: https://twitter.com/zty0826/status/1398477411000360960
    hallDrawnel
        19
    hallDrawnel  
       2021-06-02 22:26:01 +08:00
    把 key 复制出来放到 slice 排序后从 map 取值拼接。这是要做啥?某种奇怪的需要按照顺序的签名 or 校验操作?
    liyaojian
        20
    liyaojian  
    OP
       2021-06-02 22:38:52 +08:00
    @CEBBCAT #17 感谢,学到一个新词
    wjfz
        21
    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
        22
    wjfz  
       2021-06-02 22:57:30 +08:00
    如果是为了做签名,直接把 json 字符串拿去加密也是一种可选项。
    labulaka521
        23
    labulaka521  
       2021-06-03 08:30:16 +08:00 via iPhone
    像淘宝 pdd 的一些开放接口 key 的顺序都是字符序,
    Muninn
        24
    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
        25
    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
    yemoluo
        26
    yemoluo  
       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
        27
    admpubcom  
       2021-06-04 01:52:36 +08:00 via iPhone
    @GTim 直接用 for m 就行了干嘛还用反射?
    AlexSLQ
        28
    AlexSLQ  
       2021-06-08 10:04:56 +08:00
    为什么不用 struct.name+struct.userID 或者 map["name"]+map["userID"]这种,都确定就用这两种字段了就明着用呗
    HUNYXV
        29
    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
        30
    chenall  
       2021-07-10 08:51:16 +08:00 via Android
    建行的聚合支付接口。
    就是使用楼主这种逻辑进行签名。

    我是直接提前把 key 的顺序做成一个有序列表。
    然后再遍历。
    只是这样子,后面有要扩展增加字段的时候,就要重新再修改这个列表。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5574 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 43ms UTC 08:56 PVG 16:56 LAX 01:56 JFK 04:56
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86