jsongomarshallingencodego-gin

How to create a sorted key-value map that can be serialized by Gin to json?


I'm using Gin to create a REST API. The response I'm trying to create is a key-value json map such as:

    "content": {
        "1.4.5.": {
            "id": "1.4.5.",
            "content": "some content",
            "title": "title"
        },
        "1.4.6.": {
            "id": "1.4.6.",
            "content": "another content",
            "title": "another title"
        },

The data model that I'm using is:

type TopicBundle struct {
  ...
  Content      map[string]Topic `json:"content"`
}

And it is correctly serialized to json with:

c.JSON(200, topicBundle)

Almost.

The map[string]Topic never gets its values in the right order. I create it from a sorted map. But it does not help.

    var contentMap = make(map[string]Topic, sm.Len())
    for _, key := range sm.Keys() {
        contentMap[key.(string)] = first(sm.Get(key)).(Topic)
    }

At some point this map seems to be recreated and keys change their order a litte bit. I cannot think of any other alternatives as Gin seems to correctly serialize only this primitive key-value map. The sorted map from github.com/umpc/go-sortedmap is not serialized at all.

So how do I keep the order of keys in this primitive (native?) structure? Or should I write a custom serializer for Gin?

I tried to find the solution on the internet.


Solution

  • The solution in my case was to write a wrapper around sortedmap.SortedMap and a custom MarshalJSON for this wrapper:

    type TopicBundle struct {
        Content      SortedMapWrapper `json:"content"`
    }
    type SortedMapWrapper struct {
        topics *sortedmap.SortedMap
    }
    
    func (wrapper SortedMapWrapper) MarshalJSON() ([]byte, error) {
        var sb strings.Builder
        var counter = 0
        sb.WriteString("{")
        for _, key := range wrapper.topics.Keys() {
            sb.WriteString("\"")
            sb.WriteString(key.(string))
            sb.WriteString("\":")
            sb.Write(first(json.Marshal(first(wrapper.topics.Get(key)))))
            counter += 1
            if counter < wrapper.topics.Len() {
                sb.WriteString(",")
            }
        }
        sb.WriteString("}")
        return []byte(sb.String()), nil
    }
    func loadTopic(c *gin.Context) {
        var contentMap = sortedmap.New(1, comparisonFunc)
        contentMap.Insert("val1", Topic{"val1", "val2", "val3"})
        contentMap.Insert("val33", Topic{"val1", "val2", "val3"})
        var topicBundle = TopicBundle{}
        topicBundle.Content = SortedMapWrapper{contentMap}
        c.JSON(200, topicBundle)
    }
    

    Note that the definition of MarshalJSON should use SortedMapWrapper (not *SortedMapWrapper). Otherwise it will not be found.