V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
reexamine

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

  •  
  •   reexamine · Jun 2, 2021 · 3924 views
    This topic created in 1802 days ago, the information mentioned may be changed or developed.

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

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

    Supplement 1  ·  Jun 2, 2021
    非常感谢各位献计! 8 楼的方案可以解决我的问题。可能是表述不准确,应该是:由于 map 的无序,想求个其他方案解题。

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

    再次感谢各位大佬。
    30 replies    2021-07-10 08:51:16 +08:00
    Jason0803
        1
    Jason0803  
       Jun 2, 2021
    为什么不用 struct
    sunny352787
        2
    sunny352787  
       Jun 2, 2021
    确定参数就用 struct,不确定就全取出来塞到 slice 里排序再用
    hello2060
        3
    hello2060  
       Jun 2, 2021
    不懂 go, 但是你既然只能转到 map, map 里的 key 不是按照输入顺序排序, 那 map 是没法解决这问题的.
    reexamine
        4
    reexamine  
    OP
       Jun 2, 2021
    @Jason0803 #1
    @sunny352787 #2
    struct 也不能确定用户传过来的 name 和 user_id 的顺序啊,我需要获取 name 和 user_id 对应顺序的值拼接字符串,不同的顺序拼接的字符串不一样
    reexamine
        5
    reexamine  
    OP
       Jun 2, 2021
    @hello2060 #3 所以问下论坛里的大佬有没有其他方法解决这个问题,jsonStr 是用户的传参,这个不能变
    virusdefender
        6
    virusdefender  
       Jun 2, 2021
    如果只有这么两个字段的话,正则处理下就好了
    lujjjh
        7
    lujjjh  
       Jun 2, 2021   ❤️ 1
    很奇怪的需求,但也是有办法的: https://play.golang.org/p/PAYLlXZjhgF
    Kisesy
        8
    Kisesy  
       Jun 2, 2021   ❤️ 1
    sphawkcn
        9
    sphawkcn  
       Jun 2, 2021
    @lujjjh #7 在以前的 AAuto (现在叫 aardio )的圈子里,有个人跟你一样叫 lujjjh,请问是你吗?我的朋友
    xiaoyiyu
        10
    xiaoyiyu  
       Jun 2, 2021   ❤️ 1
    struct + String() 不就可以了 https://play.golang.org/p/V6RXkf-sceA
    hello2060
        11
    hello2060  
       Jun 2, 2021
    @liyaojian 如果只要能 work 就行, 你搜整个 string 看那个在前哪个在后就可以了, 搜"name":和"user_id":
    hello2060
        12
    hello2060  
       Jun 2, 2021   ❤️ 1
    我不懂 go, 随便搜了下, 问题也没仔细看 https://play.golang.org/p/yZ5DxZLIMXC 这个是不是可以你看看
    rekulas
        13
    rekulas  
       Jun 2, 2021   ❤️ 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  
       Jun 2, 2021
    才发现跟#8 的重复了
    SorcererXW
        15
    SorcererXW  
       Jun 2, 2021   ❤️ 1
    直接用正则表达式把所有 key 提取出来就能知道顺序了
    https://stackoverflow.com/questions/24300112/regex-to-match-keys-in-json
    CEBBCAT
        16
    CEBBCAT  
       Jun 2, 2021   ❤️ 1
    其实按照规范来说,JSON 的键值对是无序的[1],可以主张更换数据结构来规避这个“需要 JSON 的键按序解析”的问题。但假如这个用户比较顶……

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

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


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

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

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

    顺便分享一个近期的故事: https://twitter.com/zty0826/status/1398477411000360960
    hallDrawnel
        19
    hallDrawnel  
       Jun 2, 2021
    把 key 复制出来放到 slice 排序后从 map 取值拼接。这是要做啥?某种奇怪的需要按照顺序的签名 or 校验操作?
    reexamine
        20
    reexamine  
    OP
       Jun 2, 2021
    @CEBBCAT #17 感谢,学到一个新词😄
    wjfz
        21
    wjfz  
       Jun 2, 2021
    我这还有个骚操作,重新 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  
       Jun 2, 2021
    如果是为了做签名,直接把 json 字符串拿去加密也是一种可选项。
    labulaka521
        23
    labulaka521  
       Jun 3, 2021 via iPhone
    像淘宝 pdd 的一些开放接口 key 的顺序都是字符序,
    Muninn
        24
    Muninn  
       Jun 3, 2021
    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  
       Jun 3, 2021
    如果这是个语言问题,我比较赞同 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  
       Jun 3, 2021
    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  
       Jun 4, 2021 via iPhone
    @GTim 直接用 for m 就行了干嘛还用反射?
    AlexSLQ
        28
    AlexSLQ  
       Jun 8, 2021
    为什么不用 struct.name+struct.userID 或者 map["name"]+map["userID"]这种,都确定就用这两种字段了就明着用呗
    HUNYXV
        29
    HUNYXV  
       Jun 11, 2021
    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  
       Jul 10, 2021 via Android
    建行的聚合支付接口。
    就是使用楼主这种逻辑进行签名。

    我是直接提前把 key 的顺序做成一个有序列表。
    然后再遍历。
    只是这样子,后面有要扩展增加字段的时候,就要重新再修改这个列表。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   3038 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 68ms · UTC 13:35 · PVG 21:35 · LAX 06:35 · JFK 09:35
    ♥ Do have faith in what you're doing.