英文:
How to skip JSON fields in Struct on input and display them on output, and accept certain fields in input and skip them in output in Golang?
问题
我正在使用Echo框架和Gorm编写一个Go语言的Web服务。
我有一个名为User的结构体,如下所示:
type User struct {
ID uint `json:"user_id"`
Email string `json:"email_address,omitempty" validate:"required,email"`
}
我接受POST请求,请求的Content-type为application/json。
我希望我的输入是{"email_address": "email@email.com"},输出是{"user_id": 1}。
如何禁止用户在请求中提交ID(以防止他们创建具有特定ID的记录),但保留响应中的ID呢?
目前我在保存之前执行user.ID = 0,但我想知道是否有更好的方法?
我还想在输出中跳过Email字段。目前我在输出之前执行user.Email = "",是否有更好的方法?
谢谢!
英文:
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 IDs), 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!
答案1
得分: 3
icza的答案提供了一个不错的解决方案,你也可以使用JSON编组辅助方法MarshalJSON/UnmarshalJSON:
func main() {
// 使用辅助的json方法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,
})
}
这将给出以下结果:
[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}
在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.
答案2
得分: 2
我通过将示例更加通用化,并展示了更一般问题的优雅解决方案来扩展了你的示例。
假设在User中有以下情况:
- 一些字段只能从输入中解析
- 一些字段只能出现在输出中
- 还有一些字段既需要从输入中解析,也需要出现在输出中
你的示例是这个问题的一个"子集"(因为在你的示例中没有共同的字段)。
可以通过使用嵌入来优雅地解决这个问题,而无需重复字段。你可以创建3个单独的类型:一个用于共同字段UserBoth,一个用于仅在输入中的字段UserIn,一个用于仅在输出中的字段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"`
}
请注意,UserBoth(或者更准确地说是*UserBoth,以避免重复结构值)在UserIn和UserOut中都被嵌入,因为我们希望这些字段都出现在两者中。
现在,如果你有一个包含所有字段的输入(即使我们不想要所有字段):
const in = `{"name":"Bob","age":23,"Email":"as@as.com","id":123}`
将其解组为UserIn的值将只解析你想要的内容:
uin := UserIn{UserBoth: &UserBoth{}}
err := json.Unmarshal([]byte(in), &uin)
fmt.Printf("%+v %+v %v\n", uin, uin.UserBoth, err)
输出结果(注意Email字段被解析,但ID字段没有):
{UserBoth:0x1050e150 Email:as@as.com} &{Name:Bob Age:23} <nil>
当你想要生成输出时:
uout := UserOut{UserBoth: uin.UserBoth}
// 从数据库获取/设置ID:
uout.ID = 456
out, err := json.Marshal(uout)
fmt.Println(string(out), err)
输出结果(注意ID字段存在,但Email字段不存在):
{"name":"Bob","age":23,"id":456} <nil>
你可以在Go Playground上尝试一下。
使用"统一"的User
上面的示例使用了两个不同的结构体:UserIn用于解析,UserOut用于生成输出。
如果在你的代码中你想要使用一个"统一"的User结构体,可以这样做:
type User struct {
UserIn
UserOut
}
使用它:
uboth := &UserBoth{}
u := User{UserIn{UserBoth: uboth}, UserOut{UserBoth: uboth}}
err := json.Unmarshal([]byte(in), &u.UserIn)
fmt.Printf("%+v %+v %v\n", u, u.UserIn.UserBoth, err)
// 从数据库获取/设置ID:
u.ID = 456
out, err := json.Marshal(u.UserOut)
fmt.Println(string(out), err)
你可以在Go Playground上尝试这个示例。
英文:
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:
- some fields that must be parsed only from the input
- some fields that must appear only in the output
- and there are some fields that must be parsed from input and must also appear in output
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\n", 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.
Having a "unified" 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\n", 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论