DEV Community

Eray Ates
Eray Ates

Posted on

Embed map in JSON output

The easy way to embed a map to struct's JSON output is to implement a custom MarshalJSON function.

First, let's define a small struct:

type test struct {
    KeyValue map[string]interface{}
    Info     string
}
Enter fullscreen mode Exit fullscreen mode

Concrete struct and run json.Marshal to see the result.

myStruct := test{
    Info: "Testing Struct",
}

myStruct.KeyValue = make(map[string]interface{})

myStruct.KeyValue["language"] = "go"
myStruct.KeyValue["year"] = 2007

// print result
rest, err := json.Marshal(myStruct)
if err != nil {
    println(err)

    return
}

println(string(rest))
Enter fullscreen mode Exit fullscreen mode

So the result is clearly:

{"KeyValue":{"language":"go","year":2007},"Info":"Testing Struct"}
Enter fullscreen mode Exit fullscreen mode

Now adding a MarshalJSON method to our struct and embed the KeyValue part in the output.

func (m test) MarshalJSON() ([]byte, error) {
Enter fullscreen mode Exit fullscreen mode

This first interface is used without a pointer of the test, so this is more useful. If you use a pointer method, you should give the address of your concrete struct to json.Marshal.

Then continue to implement, make same as before output.

func (m test) MarshalJSON() ([]byte, error) {
    type newTestType test

    // marshal everything with json's original method
    preByte, err := json.Marshal(&struct {
        *newTestType
    }{
        newTestType: (*newTestType)(&m),
    })

    return preByte, err
}
Enter fullscreen mode Exit fullscreen mode

Here, we cannot use json.Marshal(m) in our MarshalJSON method. Because it always triggers our method, it will make a fatal error: stack overflow. So we are adding a new type based on our type and now the new type doesn't have MarshalJSON method, and we can use it in json.Marshal.

Now we got the same output as before:

{"KeyValue":{"language":"go","year":2007},"Info":"Testing Struct"}
Enter fullscreen mode Exit fullscreen mode

So after that, we can add everything directly to preByte value to affect the result. But first, we need to disable KeyValue field in our output because we will add it manually.

Add JSON tags to struct:

type test struct {
    KeyValue map[string]interface{} `json:"-"`
    Info     string                 `json:"info"`
}
Enter fullscreen mode Exit fullscreen mode

Now we cannot see the KeyValue in the output of json.Marshal.

Append our bytes and new custom MarshalJSON method:

func (m test) MarshalJSON() ([]byte, error) {
    // using to disable recursive this function
    type newTestType test

    // marshal everything with json's original method
    preByte, err := json.Marshal(&struct {
        *newTestType
    }{
        newTestType: (*newTestType)(&m),
    })

    if err != nil {
        return nil, err
    }

    buffer := bytes.NewBuffer([]byte{})

    // delete close bracket
    buffer.Write(preByte[:len(preByte)-1])

    // add new values to buffer
    length := len(m.KeyValue)
    count := 0

    if length > 0 {
        buffer.WriteString(",")
    }

    for key, value := range m.KeyValue {
        jsonValue, err := json.Marshal(value)
        if err != nil {
            return nil, err
        }

        buffer.WriteString(fmt.Sprintf("\"%s\":%s", key, string(jsonValue)))
        count++

        if count < length {
            buffer.WriteString(",")
        }
    }

    buffer.WriteString("}")

    return buffer.Bytes(), nil
}
Enter fullscreen mode Exit fullscreen mode

In here, get an empty buffer and fill our original json.Marshal output and delete the last character, which is } in our case after that added manually to our byte buffer. Used json.Marshal again for values of map.

Result

{"info":"Testing Struct","language":"go","year":2007}
Enter fullscreen mode Exit fullscreen mode

But there is a one missing part we don't check duplicate keys. If your map has "info" key you will see twice!

To check all code

https://play.golang.org/p/hDIqyijyYhS

https://gist.github.com/rytsh/05f14c9e7ec364a5a108806339b85b09

Discussion (0)