简体   繁体   中英

How to embed a method result into JSON output when marshalling a type?

I'm seeking a clean approach to embed the return value of a method into the JSON marshalling of a type/value. It would be great if I don't need to write custom JSON marshaller.

For example, if the User type has FirstName and LastName fields and a FullName() method, how can I easily embed a full_name field into JSON output?

 type User struct {
     FirstName string `json:"first_name"`
     LastName  string `json:"last_name"`
 }

 func (u User) FullName() string {
     return fmt.Sprintf("%s %s", u.FirstName, u.LastName)
 }

Expected JSON:

 {
     "first_name": "John",
     "last_name":  "Smith",
     "full_name":  "John Smith"
 }

This cannot be easily handled without providing some marshaller. I understand you don't want to write a MarshalJSON and do everything manually, but you can try to extend your structure in the custom marshaller and than rely on the default one. Proof of concept:

type User struct {
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
}

func (u *User) FullName() string {
    return fmt.Sprintf("%s %s", u.FirstName, u.LastName)
}

func (u User) MarshalJSON() ([]byte, error) {
    type rawUser User // raw struct, without methods (and marshaller)       
    // Marshal rawUser with an extension
    return json.Marshal(struct {
        rawUser
        FullName string `json:"full_name"`
    }{rawUser(u), u.FullName()})
}

[ play ]

You need to cast User to rawUser to strip all methods – otherwise you would have infinite loop of MarshalJSON . Also I've chosen MarshalJSON to operate on copy rather than pointer to make sure json.Marshal(user) will yield the same result as json.Marshal(&user) .

This is not a one liner, but hides the complexity behind a standard interface, so you don't need to remember there's a special, non-standard way of converting your structure to JSON.

You can create a new type and encode that to JSON. If you include an anonymous field of type *User , the two get merged:

type UserForJSON struct {
    *User
    FullName string `json:"full_name"`
}

func NewUserForJSON(u *User) *UserForJSON {
    return &UserForJSON{u, u.FullName()}
}

func main() {
    u := &User{"John", "Smith"}
    j, _ := json.Marshal(NewUserForJSON(u))
    fmt.Print(string(j))

}

Playground link .

It would be nice if we could let User implement json.Marshaller instead, and let User.MarshalJSON() create a UserForJSON object under the hood, but that leads to infinite recursion.

Im not sure if its the "nicest" way to do it but it`s very simple one.

func (u User) FullNameMarshal() []byte {
    u.FullName = u.FirstName + " " + u.LastName
    uj, err := json.Marshal(&u)
    if err != nil {
        fmt.Println(err)
    }
    return uj
}

GO PLAYGROUND

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM