0

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
}
ZARKONES
  • 61
  • 1
  • 7
  • 2
    You want order? Then use structs, because maps in Go are unordered, by design. See [Map types spec](https://go.dev/ref/spec#Map_types): *"A map is an **unordered** group of elements... "*. – mkopriva Jan 08 '22 at 17:29
  • Identifiers are generated during the run-time. Having a predefined structure is not an option. @mkopriva – ZARKONES Jan 08 '22 at 17:30
  • I can confirm that maps do sort keys in an alphabetic order. @mkopriva – ZARKONES Jan 08 '22 at 17:33
  • 2
    No, that's `json.Marshal` doing the sorting, maps are unordered. You can search for the word "sort" on this [page](https://pkg.go.dev/encoding/json), it explains why you're getting map keys ordered in the resulting json object. – mkopriva Jan 08 '22 at 17:34
  • You're actually probably right. But that still doesn't answers the question. Have any suggestions? – ZARKONES Jan 08 '22 at 17:36
  • 1
    Store keys and values in a slice. Encode the slice to a JSON object using [json.Encoder](https://pkg.go.dev/encoding/json#Encoder). Use the [Encoder.Encode](https://pkg.go.dev/encoding/json#Encoder.Encode) method to write keys and values. – Charlie Tumahai Jan 08 '22 at 17:36
  • May I ask you to provide a code snipped, please? Since I see where you're going with this, but I'm not sure of the code implementation. – ZARKONES Jan 08 '22 at 17:39
  • 1
    Hard to provide a valid suggestion if one doesn't know what the source of the data is and what the desired order is. As you now know, Go maps are unordered. So If you are starting with a Go map, you are already out of luck as far as controlling the order goes. – mkopriva Jan 08 '22 at 17:40
  • 1
    Edit the question to show how you are storing the ordered keys and values. – Charlie Tumahai Jan 08 '22 at 17:40
  • I've provided the code snippet as requested. @CeriseLimón – ZARKONES Jan 08 '22 at 17:48
  • If that's the case I'm willing to move away from maps. To make it more clear, I wish to preserve to order of keys in the manner that they're generated, question has been updated with a code snippet. Please, take a look. Thanks. @mkopriva – ZARKONES Jan 08 '22 at 17:50
  • @CeriseLimón Which structure do you recommend me on using instead? – ZARKONES Jan 08 '22 at 17:56
  • 1
    Instead of type `map[string]string` for the `jsonObject` variable try the `object` type defined [here](https://go.dev/play/p/DxyGnTtsQun). It's a slice of key value pairs that implements the `json.Marshaler` interface to output a JSON object instead of a JSON array. I assume there are ways to optimize the byte-appends but you can worry about that once you profile it and confirm that it's something that harms the performance of your app. – mkopriva Jan 08 '22 at 17:58

1 Answers1

1

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
    }
ZARKONES
  • 61
  • 1
  • 7