老生常谈,如何给go的json key排序

2024-06-06 09:42:57 浏览数 (2)

在go中,解析json一直是一个让人痛苦的话题。尤其是对不特定的json对象,将它解析到map[string]any 对象时,key会发生乱序。

大部分场景中,这不会有什么问题,但有一些涉及签名的场景,则可能产生错误。有时需要对Marshal后产生的字符串json,按特定key顺序展示。

方法一,使用jsonvalue库

这个库是腾讯的老哥开发的,功能非常强大。尤其对json排序有很丰富的接口。MustMarshal可以传入丰富的配置。

  1. 简单通用,甚至允许自定义排序
  2. 性能会略差一些,约是官方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
}

0 人点赞