Given the following code: (reproduced here at play.golang.org.)
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Working playground link: http://play.golang.org/p/_r-bQIw347
The gist of it is this; by using the reflect package we loop over the fields of the struct we wish to serialize and map them to a map[string]interface{} we can now retain the flat structure of the original struct without introducing new fields.
Caveat emptor, there should probably be several checks against some of the assumptions made in this code. For instance it assumes that MarshalHateoas always receives pointers to values.
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type User struct {
Id int `json:"id"`
Name string `json:"name"`
}
type Session struct {
Id int `json:"id"`
UserId int `json:"userId"`
}
func MarshalHateoas(subject interface{}) ([]byte, error) {
links := make(map[string]string)
out := make(map[string]interface{})
subjectValue := reflect.Indirect(reflect.ValueOf(subject))
subjectType := subjectValue.Type()
for i := 0; i < subjectType.NumField(); i++ {
field := subjectType.Field(i)
name := subjectType.Field(i).Name
out[field.Tag.Get("json")] = subjectValue.FieldByName(name).Interface()
}
switch s := subject.(type) {
case *User:
links["self"] = fmt.Sprintf("http://user/%d", s.Id)
case *Session:
links["self"] = fmt.Sprintf("http://session/%d", s.Id)
}
out["_links"] = links
return json.MarshalIndent(out, "", " ")
}
func main() {
u := &User{123, "James Dean"}
s := &Session{456, 123}
json, err := MarshalHateoas(u)
if err != nil {
panic(err)
} else {
fmt.Println("User JSON:")
fmt.Println(string(json))
}
json, err = MarshalHateoas(s)
if err != nil {
panic(err)
} else {
fmt.Println("Session JSON:")
fmt.Println(string(json))
}
}