【问题标题】:golang - elegant way to omit a json property from being serializedgolang - 从序列化中省略 json 属性的优雅方式
【发布时间】:2020-05-19 02:05:57
【问题描述】:

我有一个用户结构,其中包含密码等敏感字段:

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

现在我希望能够使用此结构来注册用户并更新、删除以及查看。我不想要的是对密码进行序列化以供查看。当然,我可以制作自定义编组器,但这是唯一的方法吗?我尝试使用 json:"-" 选项,但这会导致它在解组时也被忽略,这是我不想要的。有没有更好的办法?

编辑: 为了让你们中的一些人放心,我当然不会以明文形式存储密码。它是密码的 bcrypt 哈希,但仍然如此。我不希望在搜索用户时返回它。

【问题讨论】:

  • 或者您可以使用 2 个结构,一个带有“公共”信息,另一个是“完整”版本,它嵌入了公共并添加了“私人”信息,例如密码。并且在查看时,只发送/序列化嵌入的公共结构。示例见相关问题:Assign struct with another struct.
  • 哦,这可能是最接近“优雅”的方式,尤其是现在您可以转换具有不同标签的相同结构。我仍然会将其标记为“自定义封送器”,但它比 JSONMarshaler 实现更简单。
  • 在 C# 和 Java 中你会使用 DTO 模式

标签: json go


【解决方案1】:

我会选择另一个结构和组合。

密码不应该以纯文本形式存储,它们应该被安全地散列(bcrypt、pbkdf2 等)。该哈希是必须存储的并且永远不应该序列化的哈希。通过使用组合,您可以执行以下操作:

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"`
}

这也为您提供了更大的灵活性。例如,如果您要求用户确认密码,您可以像这样简单地更改 UserFormData 结构:

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

这还具有将序列化细节保留在用户对象之外的优点。

【讨论】:

    【解决方案2】:

    如果您想要自定义封送处理,我想说实现 json.Marshaler 是一个优雅的解决方案。在这种情况下很简单:

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

    如果您在编组时根本不需要密码字段,请在您的用户类型中添加“omitempty”。

    【讨论】:

    • 我猜,你是对的。没有办法绕过它。我尝试了不同的方式,但就优雅而言,只有这个。
    • 如果我使用 xml 或 yaml,我想要自定义编组,我该怎么办?
    【解决方案3】:

    一个简单的解决方案是在封送用户结构之前对其进行清理:

    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}
    }
    

    演示:https://play.golang.org/p/RjKVoFc9o8

    【讨论】:

      【解决方案4】:

      现在我希望能够使用此结构来注册用户并更新、删除以及查看。

      另一种解决方案是根本不在结构中存储密码。您不需要它来查看、删除或更新(通常)。

      您需要它来创建用户记录,此时您将在数据存储中存储一个哈希值。

      您需要它来验证他们的身份(在登录时),此时您可以根据数据存储中的哈希值进行验证,然后通常会发出一个令牌,他们可以使用它来继续访问服务。

      所以你只需要它的几个点,在这些点你可以简单地将它单独保存在内存中并验证身份,对于大多数操作来说,它不需要暴露或存储在结构中。这比将它放在结构中更优雅,因为它很容易在导出或日志记录中被错误地暴露出来。

      【讨论】:

      • 这是一个很好的观点。要解析 json 文档中的密码,您可以简单地解组两次。一次进入您的用户结构(没有密码字段),然后再次进入另一个只有密码字段的结构。如果这有意义的话。
      【解决方案5】:

      您可以使用 select 语句查询数据库以仅获取您喜欢的字段。

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

      还要在您的用户类型中添加omitempty,因为未包含在选择中的字段会发送空响应。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-11-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-19
        • 2013-09-15
        相关资源
        最近更新 更多