Search code examples
jsongoserializationjson-serialization

Golang. Map to JSON, but preserve the key order


I've a map object and when it's serialized using json.Marshal(myMapObjct) the Golang sorts the keys in alphabetic order, which is causing issues on how the data is being processed. It's important to preserve the key ordering once the JSON structure has been created into a slice of bytes. Is it possible to serialize it in such a way or do I need to write my own serializer for this specific edge case?

This is the code snippet responsible for generating the JSON structure:

// serializedTraffic wraps around naturalTraffic and serializes map to string.
func serializedTraffic(payload string) (string, error) {
    trafficMap := naturalTraffic(string(payload))
    traffic, err := json.Marshal(trafficMap)
    return string(traffic), err
}

    // naturalTraffic obfuscates 'payload' into JSON-like structure.
func naturalTraffic(payload string) map[string]string {
    // Decide on how many keys there will be in the JSON structure.
    indexChar := 0
    maxChars := 126
    minChars := 16

    var jsonObject = make(map[string]string)

    // Build the JSON structure.
    for indexChar < len(payload) {
        rand.Seed(time.Now().UnixNano())
        chunkSize := rand.Intn(maxChars-minChars) + minChars
        if len(payload) < indexChar+chunkSize {
            chunkSize = len(payload) - indexChar
        }
        key := randomPopularWord()
        jsonObject[key] = base64.StdEncoding.EncodeToString([]byte(payload[indexChar : indexChar+chunkSize]))
        indexChar += chunkSize
    }

    return jsonObject
}

Solution

  • As per suggestions I'm providing a different structure which fixes the code.

        type elem struct{ key, val string }
    
        type object []elem
    
        func (o object) MarshalJSON() (out []byte, err error) {
            if o == nil {
                return []byte(`null`), nil
            }
            if len(o) == 0 {
                return []byte(`{}`), nil
            }
    
            out = append(out, '{')
            for _, e := range o {
                key, err := json.Marshal(e.key)
                if err != nil {
                    return nil, err
                }
                val, err := json.Marshal(e.val)
                if err != nil {
                    return nil, err
                }
                out = append(out, key...)
                out = append(out, ':')
                out = append(out, val...)
                out = append(out, ',')
            }
            // replace last ',' with '}'
            out[len(out)-1] = '}'
            return out, nil
        }
    
        // serializedTraffic wraps around naturalTraffic and serializes map to string.
        func serializedTraffic(payload string) (string, error) {
            trafficMap := naturalTraffic(string(payload))
            traffic, err := trafficMap.MarshalJSON()
            return string(traffic), err
        }
    
        // naturalTraffic obfuscates 'payload' into JSON-like structure.
        func naturalTraffic(payload string) object {
            // Decide on how many keys there will be in the JSON structure.
            indexChar := 0
            maxChars := 126
            minChars := 16
    
            var jsonObject object
    
            // Build the JSON structure.
            for indexChar < len(payload) {
                rand.Seed(time.Now().UnixNano())
                chunkSize := rand.Intn(maxChars-minChars) + minChars
                if len(payload) < indexChar+chunkSize {
                    chunkSize = len(payload) - indexChar
                }
                key := randomPopularWord()
                jsonObject = append(jsonObject, elem{
                    key: key,
                    val: base64.StdEncoding.EncodeToString([]byte(payload[indexChar : indexChar+chunkSize])),
                })
                indexChar += chunkSize
            }
    
            return jsonObject
        }