golang - elegant way to omit a json property from being serialized

狂风中的少年 提交于 2020-12-05 12:15:39

问题


I've got a user struct, which has sensitive fields like password:

type User struct {
    UID string `json:"uid"  binding:"required"`
    Password string `json:"password" binding:"required"`
    EmailAddress string `json:"email" binding:"required"`
}

Now I want to be able to use this struct to register a user and update, delete but also to view. What I don't want is for the password to be serialized for viewing. I can, of course, make a custom marshaller but is that the only way? I tried using the json:"-" option but that causes it to be ignored while unmarshalling as well, which I don't want. Is there a better way?

EDIT: To put some of you guys at ease, I'm NOT going to be storing the password in plaintext, of course. It's the bcrypt hash of the password, but still. I don't want it to be returned when I search for users.


回答1:


I'd say implementing json.Marshaler is the elegant solution if you want custom marshaling. It's quite simple in this case:

func (u User) MarshalJSON() ([]byte, error) {
    type user User // prevent recursion
    x := user(u)
    x.Password = ""
    return json.Marshal(x)
}

Add "omitempty" in your User type if you don't want the password field at all when marshaling.




回答2:


I would go with another struct and composition.

Password should never be stored in plain-text, they should be securely hashed (bcrypt, pbkdf2, etc.). That hash is the one that has to be stored and should never be serialized. By using composition, you can do something like this:

type User struct {
    UID string `json:"uid"  binding:"required"`
    HashedPassword string `json:"-"`
    EmailAddress string `json:"email" binding:"required"`
}

type UserFormData struct {
   User
   Password string `json:"password" binding:"required"`
}

This also gives you more flexibility. For instance, if you ask the user to confirm the password, you can simply change the UserFormData struct like this:

type UserFormData struct {
   User
   Password string `json:"password" binding:"required"`
   ConfirmPassword string `json:"confirm_password" binding:"required"`
}

Which also has the advantage to keep that serialization details outside your User object.




回答3:


A simple solution would be to sanitize the user struct before marshaling it:

type User struct {
    UID          string `json:"uid"  binding:"required"`
    Password     string `json:"password,omitempty" binding:"required"`
    EmailAddress string `json:"email" binding:"required"`
}

func sanitizeUser(u User) User {
    return User{u.UID, "", u.EmailAddress}
}

Demo: https://play.golang.org/p/RjKVoFc9o8




回答4:


Now I want to be able to use this struct to register a user and update, delete but also to view.

Another solution is not to store the password at all in the struct. You don't need it to view, or delete, or update (normally).

You need it to create the user record, at which point you'll store a hash in your data store.

You need it to verify their identity (on login), at which point you verify against the hash in your data store, then usually issue a token they can use to keep accessing the service.

So there are only a few points you need it, and at those points you can simply keep it in memory separately and verify identity, it doesn't need to be exposed or stored in the struct for most operations. This is IMO more elegant than having it in the struct where it can very easily be exposed by mistake in export or logging.




回答5:


You can query the database with a select statement to get only fields you like.

db.Model(&User{}).Select([]string{"uid","email"}).Find(&users)

Also add omitempty in your User type since empty response is sent for fields not included in select.



来源:https://stackoverflow.com/questions/46427723/golang-elegant-way-to-omit-a-json-property-from-being-serialized

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!