Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to keep order of keys when convert map[string]interface{} to json ? #2536

Closed
kkqy opened this issue Mar 22, 2023 · 16 comments
Closed

How to keep order of keys when convert map[string]interface{} to json ? #2536

kkqy opened this issue Mar 22, 2023 · 16 comments

Comments

@kkqy
Copy link

kkqy commented Mar 22, 2023

1. What version of Go and system type/arch are you using?

go version go1.20.2 windows/amd64

2. What version of GoFrame are you using?

v2.3.3

3. Can this issue be re-produced with the latest release?

yes

4. What did you do?

package main

import (
	"context"
	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/frame/g"
)

func main() {
	amap := map[string]string{}
	gjson.DecodeTo("{\"b\":\"b\",\"a\":\"a\"}", &amap)
	g.Log().Info(context.Background(), amap)
	result, _ := gjson.EncodeString(amap)
	g.Log().Info(context.Background(), result)
}

INPUT is:
{"b":"b","a":"a"}
OUTPUT is:
{"a":"a","b":"b"}

5. What did you expect to see?

Keep the order of keys as that in map.

package main

import (
	"context"

	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/frame/g"
)

func main() {
	amap := map[string]string{
		"c": "c",
		"b": "b",
	}
	amap["a"] = "a"
	g.Log().Info(context.Background(), amap)
	result, _ := gjson.EncodeString(amap)
	g.Log().Info(context.Background(), result)
}

I want get {"c":"c","b":"b","a":"a"} instead of {"a":"a","b":"b","c":"c"}.

6. What did you see instead?

Keys are order by alphabet.

@kkqy
Copy link
Author

kkqy commented Mar 22, 2023

This is just an example, but actual data is more complex than this.
The order of keys of my data is very important for my project.

I found some third-party packages to solve this problem,but I don't know how to integrate them into goframe "gracely".

@windvalley
Copy link
Contributor

@kkqy I couldn't reproduce the issue you described. Which specific response function did you use?

@kkqy
Copy link
Author

kkqy commented Mar 23, 2023

@kkqy I couldn't reproduce the issue you described. Which specific response function did you use?

I'm sorry, I made a mistake. The network panel of Chrome sorted the keys automatically.
The order of keys of struct is correct in fact.

package main

import (
	"context"
	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/frame/g"
)

func main() {
	amap := map[string]string{}
	gjson.DecodeTo("{\"b\":\"b\",\"a\":\"a\"}", &amap)
	g.Log().Info(context.Background(), amap)
	result, _ := gjson.EncodeString(amap)
	g.Log().Info(context.Background(), result)
}

Acctually,the original issue is that keys of map[string]string are sorted by alphabet.
This "feature" also affects the json response when response has a map[string]interface{} member.

@kkqy kkqy changed the title How to keep order of keys when convert struct to json ? How to keep order of keys when convert map[string]interface{} to json ? Mar 23, 2023
@kkqy
Copy link
Author

kkqy commented Mar 23, 2023

Slice can keep order of it's elements.
So can gjson unmarshal/marshal a type like

[]struct {
	Key string
	Value interface{}
}

which can replace map[string]interface{} in order to keep order of elements?

@LonelySally
Copy link
Member

go的map输出本身就是无序的.如果key有排序规则 可以先提取所有key做排序然后再根据排序结果append到slice中.要保持原有属性json得是数组格式[]而非对象{}

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


Go's map output itself is unordered. If the key has a sorting rule, you can first extract all the keys for sorting and then append to the slice according to the sorting result. To keep the original attribute json must be an array format [] instead of an object {}

@kkqy
Copy link
Author

kkqy commented Mar 24, 2023

go的map输出本身就是无序的.如果key有排序规则 可以先提取所有key做排序然后再根据排序结果append到slice中.要保持原有属性json得是数组格式[]而非对象{}

是的,golang中map的key是不会保证顺序的,但python和php之类的语言的key又是有顺序的。
目前我的困境在于,各个组件的数据交换是通过json格式,而又不能因为我用的golang而调整这个json的结构,因为一旦修改了,所有组件都得改,影响太大了。所以在想goframe是否支持把key有序的json对象{}解析为键值对切片,也就是用

[]struct {
	Key string
	Value interface{}
}

代替

map[string]interface{}

这样的话,我只需要修改我这个组件的struct,就能兼容其它组件传过来的有序的key的json对象了。

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


Go’s map output itself is unordered. If the key has a sorting rule, you can extract all the keys for sorting and then append to the slice according to the sorting result. To maintain the original attribute json must be an array format [] instead of an object {}

Yes, the keys of the map in golang do not guarantee the order, but the keys of languages ​​such as python and php are in order.
My current dilemma is that the data exchange of each component is in json format, and the structure of this json cannot be adjusted because of the golang I use, because once it is modified, all components have to be changed, and the impact is too great. So I wonder if goframe supports parsing the key-ordered json object {} into a key-value pair slice, that is, use

[]struct {
key string
Value interface{}
}

replace

map[string]interface{}

In this case, I only need to modify the struct of my component to be compatible with the ordered key json objects passed from other components.

@kkqy
Copy link
Author

kkqy commented Mar 24, 2023

我尝试过

func main() {
	amap := []struct {
		Key   string
		Value interface{}
	}{}
	gjson.DecodeTo("{\"b\":\"b\",\"a\":\"a\"}", &amap)
	g.Log().Info(context.Background(), amap)
	result, _ := gjson.EncodeString(amap)
	g.Log().Info(context.Background(), result)
}

但是意料之中输出是[ ]
我又试了一下:

func main() {
	amap := []interface{}{}
	gjson.DecodeTo("{\"b\":\"b\",\"a\":\"a\"}", &amap)
	g.Log().Info(context.Background(), amap)
	result, _ := gjson.EncodeString(amap)
	g.Log().Info(context.Background(), result)
}

结果依旧是空[ ],也就是说gjson无法把json对象{ } 解析为切片。

所以我希望gjson提供一个特殊的类型 KeyValuePair
用于将json的对象解析为键值对切片,这样就能保证顺序了。

@LonelySally
Copy link
Member

LonelySally commented Mar 24, 2023

依赖map/object的顺序本身就是不符合编程规范的(有顺序的只能是数组/切片,三方有序map输出基本都是基于内部的keyslice 例如gmap.NewListMap). 这个需求也仅限于你这个个例 基本不会有这方面的实现.如果有强依赖顺序应当声明key的排序方式而非依赖json编码.如果说你需要实现这个需求建议用正则去匹配json中的key自行实现. \"(.+?)\":.*?(?=[,{}])\"?

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


Relying on the order of map/object itself does not conform to the programming specification (ordered ones can only be arrays/slices, and the three-way ordered map output is basically based on internal keyslice such as gmap.NewListMap). This requirement is also limited to you There is basically no such implementation in the example. If there is a strong dependence on the order, you should declare the sorting method of the key instead of relying on the json encoding. If you need to achieve this requirement, it is recommended to use regular expressions to match the keys in json to achieve it yourself.

@kkqy
Copy link
Author

kkqy commented Mar 24, 2023

依赖map/object的顺序本身就是不符合编程规范的(有顺序的只能是数组/切片,三方有序map输出基本都是基于内部的keyslice 例如gmap.NewListMap). 这个需求也仅限于你这个个例 基本不会有这方面的实现.如果有强依赖顺序应当声明key的排序方式而非依赖json编码.如果说你需要实现这个需求建议用正则去匹配json中的key自行实现. \"(.+?)\":.*?(?=[,{}])\"?

有第三方包解决我的问题,但正如GoFrame文档组件统一化里说的,不想用多个包解决一个问题。于是我只能放弃gjson,完全使用第三方包。
我刚看了一下gjson的源码,发现它也是用golang自带的encoding/json来解析的,我看golang的issue里也讨论过这个问题。
golang/go#27179
最后感谢你的回答,目前我的问题暂时解决了,自己实现了UnmarshalJSON和MarshalJSON.

@kkqy kkqy closed this as completed Mar 24, 2023
@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


Relying on the order of map/object itself does not conform to the programming specification (ordered ones can only be arrays/slices, and the three-party ordered map output is basically based on internal keyslice such as gmap.NewListMap). This requirement is also limited to yours There is basically no such implementation in individual cases. If there is a strong dependence on the order, you should declare the key sorting method instead of relying on json encoding. If you need to achieve this requirement, it is recommended to use regular expressions to match the keys in json to achieve it yourself. \" (.+?)\":.*?(?=[,{}])\"?

There are third-party packages to solve my problem, but as stated in GoFrame document component unification, I don’t want to use multiple packages to solve one problem. So I can only give up gjson and use third-party packages completely.
I just took a look at the source code of gjson, and found that it is also parsed with the encoding/json that comes with golang. I saw that this issue was also discussed in the issue of golang.
golang/go#27179
Finally, thank you for your answer. At present, my problem has been temporarily solved, and I have implemented UnmarshalJSON and MarshalJSON.

@kkqy
Copy link
Author

kkqy commented Mar 24, 2023

依赖map/object的顺序本身就是不符合编程规范的(有顺序的只能是数组/切片,三方有序map输出基本都是基于内部的keyslice 例如gmap.NewListMap). 这个需求也仅限于你这个个例 基本不会有这方面的实现.如果有强依赖顺序应当声明key的排序方式而非依赖json编码.如果说你需要实现这个需求建议用正则去匹配json中的key自行实现. \"(.+?)\":.*?(?=[,{}])\"?

您提供的这个正则也有一点问题,就是如果遇到嵌套的json就不行了,所以解析json还是个麻烦活

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


Relying on the order of map/object itself does not conform to the programming specification (ordered ones can only be arrays/slices, and the three-party ordered map output is basically based on internal keyslice such as gmap.NewListMap). This requirement is also limited to yours There is basically no such implementation in individual cases. If there is a strong dependence on the order, you should declare the key sorting method instead of relying on json encoding. If you need to achieve this requirement, it is recommended to use regular expressions to match the keys in json to achieve it yourself. \" (.+?)\":.*?(?=[,{}])\"?

The regex you provided also has a little problem, that is, if you encounter nested json, it will not work, so parsing json is still a troublesome job

@kkqy
Copy link
Author

kkqy commented Mar 24, 2023

我部分参考别人的代码,用泛型写了一个KeyValuePairs类型,留给后来同样需要在golang里保持json的key的顺序的人。
使用时,只需要将原来的 map[string]type 替换为 KeyValuePairs[type] 即可。

https://github.com/kkqy/gokvpairs

用法:

下载gokvpairs包

go get github.com/kkqy/gokvpairs

实例代码

package main

import (
	"encoding/json"
	"fmt"

	"github.com/kkqy/gokvpairs"
)

type OldTestStruct struct {
	Name          string            `json:"name"`
	OrderedObject map[string]string `json:"ordered_object"`
}
type NewTestStruct struct {
	Name          string                          `json:"name"`
	OrderedObject gokvpairs.KeyValuePairs[string] `json:"ordered_object"`
}

func main() {
	data := []byte(`
	{
		"name": "Test",
		"ordered_object": {
			"c": "value_c",
			"b": "value_b"
		}
	}`)
	fmt.Println("OriginalData:", string(data))
	fmt.Println("====================")

	// Old
	oldTestStruct := OldTestStruct{}
	json.Unmarshal([]byte(data), &oldTestStruct)
	result, _ := json.Marshal(oldTestStruct)
	fmt.Println("Old:", string(result))
	oldTestStruct.OrderedObject["a"] = "value_a"
	result, _ = json.Marshal(oldTestStruct)
	fmt.Println("Old:", string(result))

	fmt.Println("====================")

	// New
	newTestStruct := NewTestStruct{}
	json.Unmarshal([]byte(data), &newTestStruct)
	result, _ = json.Marshal(newTestStruct)
	fmt.Println("New:", string(result))
	newTestStruct.OrderedObject = append(newTestStruct.OrderedObject, gokvpairs.KeyValuePair[string]{Key: "a", Value: "value_a"})
	result, _ = json.Marshal(newTestStruct)
	fmt.Println("New:", string(result))
}

输出结果:

OriginalData: 
        {
                "name": "Test",
                "ordered_object": {
                        "c": "value_c",
                        "b": "value_b"
                }
        }
====================
Old: {"name":"Test","ordered_object":{"b":"value_b","c":"value_c"}}
Old: {"name":"Test","ordered_object":{"a":"value_a","b":"value_b","c":"value_c"}}
====================
New: {"name":"Test","ordered_object":{"c":"value_c","b":"value_b"}}
New: {"name":"Test","ordered_object":{"c":"value_c","b":"value_b","a":"value_a"}}

@kkqy kkqy reopened this Mar 24, 2023
@kkqy kkqy closed this as completed Mar 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants