Vitaly Sazanovich
Vitaly Sazanovich

Reputation: 694

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.

Upvotes: 3

Views: 218

Answers (1)

Vitaly Sazanovich
Vitaly Sazanovich

Reputation: 694

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.

Upvotes: 3

Related Questions