Golang Design Response Struct For API
package main
import (
"encoding/json"
"fmt"
)
type Optional map[string]interface{}
type Problem struct {
Name string
Description string
}
type ProblemResponse struct {
Name string `json:"name"`
Description string `json:"description"`
Optional
}
func (problem *Problem) ToRes() *ProblemResponse {
return &ProblemResponse{
Name: problem.Name,
Description: problem.Description,
}
}
func main() {
problem := Problem{"StackOverflow", "Asking Question"}
problemRes := problem.ToRes()
problemRes.Optional = make(map[string]interface{})
problemRes.Optional["url"] = "https://stackoverflow.com"
Response(*problemRes)
}
func Response(obj interface{}) {
data, _ := json.Marshal(obj)
fmt.Println(string(data))
}
The code above will print
{
"name": "StackOverflow",
"description": "Asking Question",
"Optional": {
"url": "https://stackoverflow.com"
}
}
But what i want is this
{
"name": "StackOverflow",
"description": "Asking Question",
"url": "https://stackoverflow.com"
}
I want in the main function i can append some information to json response. Any solution for this design, it prefer that we don't change Response Function. Thank in advance !!
You could implement json.Marshaler
interface on your ProblemResponse
struct, converting everything to a flat map and encoding to JSON. If a type implements json.Marshaler
interface, the json encoder will run the MarshalJSON method instead. Here is the documentation: https://golang.org/pkg/encoding/json/#Marshaler
Example:
type Optional map[string]interface{}
type Problem struct {
Name string
Description string
}
type ProblemResponse struct {
Name string `json:"name"`
Description string `json:"description"`
Optional
}
func (p *ProblemResponse) MarshalJSON() ([]byte, error) {
// we create a flat map including problem's field and optional fields
// we copy optional first to make sure name and description are not being overwritten from the optional map
var m = make(map[string]interface{}, 2 + len(p.Optional))
for k, v := range p.Optional {
m[k] = v
}
m["name"] = p.Name
m["description"] = p.Description
return json.Marshal(m)
}
If you don't care about modifying Optional
, you could optimize by doing it in place
:
func (p *ProblemResponse) MarshalJSON() ([]byte, error) {
p.Optional["name"] = p.Name
p.Optional["description"] = p.Description
return json.Marshal(p.Optional)
}
You could write a code generator if you have multiple structure that would need to implement that kind of flattening
behaviour on MarshalJSON.
Alternatively you could use reflection and do something like that (you should complete this method by doing more checks and use json tag), I don't recommend that solution as you loose type safety:
func Flatten(s interface{}) map[string]interface{} {
v := reflect.ValueOf(s)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("expect struct %T given", s))
}
t := v.Type()
nField := t.NumField()
r := v.FieldByName("Optional").Interface().(Optional)
for i := 0; i < nField; i++ {
f := t.Field(i)
if f.Name != "Optional" {
fv := v.Field(i)
// here you could read json tags
// to get the value in the json tag instead of ToLower
r[strings.ToLower(f.Name)] = fv.Interface()
}
}
return r
}
// usage
b, err i:= json.Marshal(Flatten(problemRes))
Or maybe just use a map ?