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
}
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))
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.
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.
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:
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:
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
Top comments (0)