Embed map in JSON output
Tags: go, programming
Published: 2021-10-19 11:51:56

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:

```go
type test struct {
	KeyValue map[string]interface{}
	Info     string
}
```

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

```go
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))
```

So the result is clearly:

```
{"KeyValue":{"language":"go","year":2007},"Info":"Testing Struct"}
```

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

```go
func (m test) MarshalJSON() ([]byte, error) {
```

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.

```go
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
}
```

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"}
```

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:

```go
type test struct {
	KeyValue map[string]interface{} `json:"-"`
	Info     string                 `json:"info"`
}
```

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

Append our bytes and new custom `MarshalJSON` method:

```go
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
}
```

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}
```

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