在go中,解析json一直是一个让人痛苦的话题。尤其是对不特定的json对象,将它解析到map[string]any
对象时,key会发生乱序。
大部分场景中,这不会有什么问题,但有一些涉及签名的场景,则可能产生错误。有时需要对Marshal后产生的字符串json,按特定key顺序展示。
方法一,使用jsonvalue库
这个库是腾讯的老哥开发的,功能非常强大。尤其对json排序有很丰富的接口。MustMarshal
可以传入丰富的配置。
- 简单通用,甚至允许自定义排序
- 性能会略差一些,约是官方json.Marshal的1/10。
直接上示例:
代码语言:javascript复制// jsonvalue "github.com/Andrew-M-C/go.jsonvalue"
func TestOrderedJsonvalue(t *testing.T) {
jsonString := `{
"yolo": "covfefe",
"stuff": {
"b": "12",
"d": "654",
"a": "1"
},
"yay": 5
}`
var iterJSON any
if err := json.Unmarshal([]byte(jsonString), &iterJSON); err != nil {
t.Fatalf(err.Error())
}
v := jsonvalue.New(iterJSON)
t.Logf("v: %v", iterJSON)
// OptDefaultStringSequence 字典序
s := v.MustMarshal(jsonvalue.OptDefaultStringSequence())
t.Logf("s: %s", s)
gtest.Assert(s, `{"stuff":{"a":"1","b":"12","d":"654"},"yay":5,"yolo":"covfefe"}`)
}
方法二:实现MarshalJSON
接口
这是自定义处理json的一般方法,定义一个新类型:
代码语言:javascript复制type JSONOrderedMap map[string]any
并实现MarshalJSON
即可。
先放用例:
代码语言:javascript复制func TestJSONOrderedMapStruct(t *testing.T) {
jsonString := `{
"yolo": "covfefe",
"stuff": {
"b": "12",
"d": "654",
"a": "1"
},
"yay": 5,
"foo": ["b","c","a"]
}`
var ordered JSONOrderedMap
if err := json.Unmarshal([]byte(jsonString), &ordered); err != nil {
t.Error(err)
return
}
jsonStr, err := json.Marshal(ordered)
if err != nil {
t.Error(err)
return
}
t.Logf("JSON: %s", jsonStr)
gtest.Assert(jsonStr,
`{"foo":["b","c","a"],"stuff":{"a":"1","b":"12","d":"654"},"yay":5,"yolo":"covfefe"}`)
}
实现:
代码语言:javascript复制package jsontools
import (
"bytes"
"encoding/json"
"sort"
"strconv"
)
// JSONOrderedMap When calling Marshal, the generated json will be sorted by key
type JSONOrderedMap map[string]any
func (j JSONOrderedMap) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
if err := genJSONMap(buf, j); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func genDefaultJSON(buf *bytes.Buffer, i any) error {
switch v := i.(type) {
case string:
buf.WriteString(""")
buf.WriteString(v)
buf.WriteString(""")
case float64:
buf.WriteString(strconv.FormatFloat(v, 'f', -1, 64))
case bool:
buf.WriteString(strconv.FormatBool(v))
case nil:
buf.WriteString("null")
default:
bts, err := json.Marshal(v)
if err != nil {
return err
}
buf.Write(bts)
}
return nil
}
func genJSONArr(buf *bytes.Buffer, vals []any) error {
buf.WriteString("[")
for i, val := range vals {
if i > 0 {
buf.WriteString(",")
}
switch v := val.(type) {
case []any:
err := genJSONArr(buf, v)
if err != nil {
return err
}
case map[string]any:
err := genJSONMap(buf, v)
if err != nil {
return err
}
default:
err := genDefaultJSON(buf, v)
if err != nil {
return err
}
}
}
buf.WriteString("]")
return nil
}
func genJSONMap(buf *bytes.Buffer, j JSONOrderedMap) error {
keys := make([]string, 0)
for k := range j {
keys = append(keys, k)
}
sort.Strings(keys)
buf.WriteString("{")
for i, k := range keys {
if i > 0 {
buf.WriteString(",")
}
buf.WriteString(""")
buf.WriteString(k)
buf.WriteString("": ")
switch v := j[k].(type) {
case []any:
err := genJSONArr(buf, v)
if err != nil {
return err
}
case map[string]any:
err := genJSONMap(buf, v)
if err != nil {
return err
}
default:
err := genDefaultJSON(buf, v)
if err != nil {
return err
}
}
}
buf.WriteString("}")
return nil
}