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 · 3290 次点击
    这是一个创建于 1299 天前的主题,其中的信息可能已经有所发展或是发生改变。

    要求:需要根据用户传入的 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
    GTim
        26
    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
        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   ·   实用小工具   ·   1001 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 18:35 · PVG 02:35 · LAX 10:35 · JFK 13:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.