I'm writing a webservice in Go using Echo framework and Gorm. I have a User
struct that looks like this:
type User struct {
ID uint `json:"user_id"`
Email string `json:"email_address,omitempty" validate:"required,email"`
}
I'm accepting POST
requests with a Content-type: application/json
. I want my input to be {"email_address": "email@email.com"}
and my output to be {"user_id": 1}
How can I forbid users from submitting ID
in the request (so they can't create records with certain ID
s), but keep ID
in the response? Right now I'm doing user.ID = 0
right before save, but I wonder if there's a better way to do it?
I also want to skip Email
in output. Right now I'm doing user.Email = ""
right before the output. Is there a better way for that also?
Thanks!
I extend your example by making it more general, and I show an elegant solution to the more general problem.
Let's assume that in User
there are:
Your example is a "subset" of this (as in your example there are no common fields).
This can be elegantly solved without repeating fields using embedding. You may create 3 separate types; one for the common fields UserBoth
, one for the input-only fields UserIn
, and one for the output-only fields UserOut
:
type UserBoth struct {
Name string `json:"name"`
Age int `json:"age"`
}
type UserIn struct {
*UserBoth
Email string `json:"email"`
}
type UserOut struct {
*UserBoth
ID uint `json:"id"`
}
Note that UserBoth
(or rather *UserBoth
to avoid duplicating the struct value) is embedded in both UserIn
and UserOut
, because we want those fields in both.
Now if you have an input that contains all fields (even though we don't want all):
const in = `{"name":"Bob","age":23,"Email":"as@as.com","id":123}`
Unmarshaling into a value of UserIn
will only parse what you want:
uin := UserIn{UserBoth: &UserBoth{}}
err := json.Unmarshal([]byte(in), &uin)
fmt.Printf("%+v %+v %v
", uin, uin.UserBoth, err)
Output (note that the Email
field is parsed but ID
isn't):
{UserBoth:0x1050e150 Email:as@as.com} &{Name:Bob Age:23} <nil>
And when you want to generate output:
uout := UserOut{UserBoth: uin.UserBoth}
// Fetch / set ID from db:
uout.ID = 456
out, err := json.Marshal(uout)
fmt.Println(string(out), err)
Output (note that the ID
field is present but Email
isn't):
{"name":"Bob","age":23,"id":456} <nil>
Try it on the Go Playground.
User
The above example used 2 different structs: UserIn
for parsing and UserOut
to generate the output.
If inside your code you want to use a "unified" User
struct, this is how it can be done:
type User struct {
UserIn
UserOut
}
Using it:
uboth := &UserBoth{}
u := User{UserIn{UserBoth: uboth}, UserOut{UserBoth: uboth}}
err := json.Unmarshal([]byte(in), &u.UserIn)
fmt.Printf("%+v %+v %v
", u, u.UserIn.UserBoth, err)
// Fetch / set ID from db:
u.ID = 456
out, err := json.Marshal(u.UserOut)
fmt.Println(string(out), err)
Try this one on the Go Playground.
While icza's answer proposes a nice solution, you could also employ JSON marshaling auxiliary methods MarshalJSON/UnmarshalJSON
:
func main() {
// employing auxiliary json methods MarshalJSON/UnmarshalJSON
user := User{ID: 123, Email: `abc@xyz.com`}
js, _ := json.Marshal(&user)
log.Printf("%s", js)
input := UserInput(user)
js, _ = json.Marshal(&input)
log.Printf("%s", js)
output := UserOutput(user)
js, _ = json.Marshal(&output)
log.Printf("%s", js)
}
type User struct {
ID uint `json:"user_id"`
Email string `json:"email_address,omitempty" validate:"required,email"`
}
type UserInput User
func (x *UserInput) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Email string `json:"email_address,omitempty" validate:"required,email"`
}{
Email: x.Email,
})
}
type UserOutput User
func (x *UserOutput) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID uint `json:"user_id"`
}{
ID: x.ID,
})
}
Which gives us:
[ info ] main.go:25: {"user_id":123,"email_address":"abc@xyz.com"}
[ info ] main.go:29: {"email_address":"abc@xyz.com"}
[ info ] main.go:33: {"user_id":123}
On Go Playground.