如何在输入时跳过Struct中的JSON字段并在输出中显示它们,并在Golang中接受输入中的某些字段并在输出中跳过它们?

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!

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
", 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
", 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.