Given the following structs:
type Person {
Name string `json:\"name\"`
}
type Employee {
Person
JobRole string `json:\"jobRole\"`
}
<
A more generic way to support massive fields in both inner and outer fields.
The side effect is you need to write this for every outer structs.
Example: https://play.golang.org/p/iexkUYFJV9K
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
)
func Struct2Json2Map(obj interface{}) (map[string]interface{}, error) {
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
var kvs map[string]interface{}
err = json.Unmarshal(data, &kvs)
if err != nil {
return nil, err
}
return kvs, nil
}
type Person struct {
Name string `json:"-"`
}
func (p Person) MarshalJSONHelper() (map[string]interface{}, error) {
return Struct2Json2Map(struct {
Name string `json:"name"`
}{
Name: strings.ToUpper(p.Name),
})
}
type Employee struct {
Person
JobRole string `json:"jobRole"`
}
func (e Employee) MarshalJSON() ([]byte, error) {
personKvs, err := e.Person.MarshalJSONHelper()
if err != nil {
return nil, err
}
type AliasEmployee Employee
kvs, err := Struct2Json2Map(struct {
AliasEmployee
} {
AliasEmployee(e),
})
for k,v := range personKvs {
kvs[k] = v
}
return json.Marshal(kvs)
}
func main() {
bob := Employee{
Person: Person{
Name: "Bob",
},
JobRole: "Sales",
}
output, err := json.Marshal(bob)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(output))
}
While this produces a different output than what the OP wants, I think it is still useful as a technique to prevent MarshalJSON
of embedded structs from breaking the marshaling of structs that contain them.
There is a proposal for encoding/json to recognize an inline option in struct tags. If that ever gets implemented, then I think avoiding embedding structs for cases like in OP's example might be the best bet.
Currently, a reasonable workaround was described in a comment on the Go issue tracker and is the basis for this answer. It consists of defining a new type that will have the same memory layout as the original struct being embedded, but none of the methods:
https://play.golang.org/p/BCwcyIqv0F7
package main
import (
"encoding/json"
"fmt"
"strings"
)
type Person struct {
Name string `json:"name"`
}
func (p *Person) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Name string `json:"name"`
}{
Name: strings.ToUpper(p.Name),
})
}
// person has all the fields of Person, but none of the methods.
// It exists to be embedded in other structs.
type person Person
type EmployeeBroken struct {
*Person
JobRole string `json:"jobRole"`
}
type EmployeeGood struct {
*person
JobRole string `json:"jobRole"`
}
func main() {
{
p := Person{"Bob"}
e := EmployeeBroken{&p, "Sales"}
output, _ := json.Marshal(e)
fmt.Printf("%s\n", string(output))
}
{
p := Person{"Bob"}
e := EmployeeGood{(*person)(&p), "Sales"}
output, _ := json.Marshal(e)
fmt.Printf("%s\n", string(output))
}
}
Outputs:
{"name":"BOB"}
{"name":"Bob","jobRole":"Sales"}
The OP wants {"name":"BOB","jobRole":"Sales"}
. To achieve that, one would need to "inline" the object returned by Person.MarshalJSON
into the object produced by Employee.MashalJSON
, excluding the fields defined in Person
.
Nearly 4 years later, I've come up with an answer that is fundamentally similar to @jcbwlkr's, but does not require the intermediate unmarshal/re-marshal step, by using a little bit of byte-slice manipulation to join two JSON segments.
func (e *Employee) MarshalJSON() ([]byte, error) {
pJSON, err := e.Person.MarshalJSON()
if err != nil {
return nil, err
}
eJSON, err := json.Marshal(map[string]interface{}{
"jobRole": e.JobRole,
})
if err != nil {
return nil, err
}
eJSON[0] = ','
return append(pJSON[:len(pJSON)-1], eJSON...), nil
}
Additional details and discussion of this approach here.
I've used this approach on parent structs to keep the embedded struct from overriding marshaling:
func (e Employee) MarshalJSON() ([]byte, error) {
v := reflect.ValueOf(e)
result := make(map[string]interface{})
for i := 0; i < v.NumField(); i++ {
fieldName := v.Type().Field(i).Name
result[fieldName] = v.Field(i).Interface()
}
return json.Marshal(result)
}
It's handy but nests the embedded structs in the output::
{"JobRole":"Sales","Person":{"name":"Bob"}}
For a tiny struct like the one in the question, @Flimzy's answer is good but can be done more succinctly:
func (e Employee) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"jobRole": e.JobRole,
"name": e.Name,
})
}
Don't put MarshalJSON
on Person
since that's being promoted to the outer type. Instead make a type Name string
and have Name
implement MarshalJSON
. Then change Person
to
type Person struct {
Name Name `json:"name"`
}
Example: https://play.golang.org/p/u96T4C6PaY
Update
To solve this more generically you're going to have to implement MarshalJSON
on the outer type. Methods on the inner type are promoted to the outer type so you're not going to get around that. You could have the outer type call the inner type's MarshalJSON
then unmarshal that into a generic structure like map[string]interface{}
and add your own fields. This example does that but it has a side effect of changing the order of the final output fields
https://play.golang.org/p/ut3e21oRdj