Golang + MongoDB嵌入类型(在一个结构体中嵌入另一个结构体)

huangapple go评论89阅读模式
英文:

Golang + MongoDB embedded type (embedding a struct in another struct)

问题

假设我运行一个API,当用户对用户资源发出GET请求时,我将返回相关字段作为JSON。

type User struct {
  Id      bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
  Name    string        `json:"name,omitempty" bson:"name,omitempty"`
  Secret  string        `json:"-,omitempty" bson:"secret,omitempty"`
}

如你所见,User中的Secret字段有json:"-"。这意味着在大多数操作中,我不希望返回该字段。在这种情况下,响应将是:

{
  "id": 1,
  "Name": "John"
}

由于json:"-"省略了该字段,因此不会返回Secret字段。

现在,我正在开放一个仅限管理员的路由,我希望返回Secret字段。然而,这意味着需要复制User结构体。

我的当前解决方案如下:

type adminUser struct {      
  Id      bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
  Name    string        `json:"name,omitempty" bson:"name,omitempty"`
  Secret  string        `json:"secret,omitempty" bson:"secret,omitempty"`
}

是否有一种将User嵌入到adminUser中的方法?类似于继承:

type adminUser struct {      
  User
  Secret  string        `json:"secret,omitempty" bson:"secret,omitempty"`
}

上述方法目前不起作用,因为在这种情况下只会返回Secret字段。

注意:在实际的代码库中,有几十个字段。因此,复制代码的成本很高。

实际的Mongo查询如下:

func getUser(w http.ResponseWriter, r *http.Request) {
  ....省略的代码....

  var user adminUser
  err := common.GetDB(r).C("users").Find(
      bson.M{"_id": userId},
  ).One(&user)
  if err != nil {
      return
  }
  common.ServeJSON(w, &user)
}
英文:

Hypothetical, I run an API and when a user makes a GET request on the user resource, I will return relevant fields as a JSON

type User struct {
  Id      bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
  Name    string        `json:"name,omitempty" bson:"name,omitempty"`
  Secret  string        `json:"-,omitempty" bson:"secret,omitempty"`
}

As you can see, the Secret field in User has json:"-". This implies that in most operation that I would not like to return. In this case, a response would be

{
  "id":1,
  "Name": "John"
}

The field secret will not be returned as json:"-" omits the field.

Now, I am openning an admin only route where I would like to return the secret field. However, that would mean duplicating the User struct.

My current solution looks like this:

type adminUser struct {      
  Id      bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
  Name    string        `json:"name,omitempty" bson:"name,omitempty"`
  Secret  string        `json:"secret,omitempty" bson:"secret,omitempty"`
}

Is there a way to embed User into adminUser? Kind of like inheritance:

type adminUser struct {      
  User
  Secret  string        `json:"secret,omitempty" bson:"secret,omitempty"`
}

The above currently does not work, as only the field secret will be returned in this case.

Note: In the actual code base, there are few dozens fields. As such, the cost of duplicating code is high.

The actual mongo query is below:

func getUser(w http.ResponseWriter, r *http.Request) {
  ....omitted code...

  var user adminUser
  err := common.GetDB(r).C("users").Find(
  	  bson.M{"_id": userId},
  ).One(&user)
  if err != nil {
	  return
  }
  common.ServeJSON(w, &user)
}

答案1

得分: 69

你应该查看bson包的inline标志(在bson.Marshal下有文档记录)。它应该允许你做类似这样的事情:

type adminUser struct {
    User `bson:",inline"`
    Secret string `json:"secret,omitempty" bson:"secret,omitempty"`
}

然而,现在你会注意到,当你尝试使用这个结构从数据库中读取时,会出现重复键错误,因为adminUserUser都包含secret键。

在你的情况下,我会从User中删除Secret字段,只在adminUser中保留一个。然后,每当你需要写入secret字段时,确保你使用的是adminUser

英文:

You should take a look at the bson package's inline flag
(that is documented under bson.Marshal).
It should allow you to do something like this:

type adminUser struct {
    User `bson:",inline"`
    Secret string `json:"secret,omitempty" bson:"secret,omitempty"`
}

However, now you'll notice that you get duplicate key errors
when you try to read from the database with this structure,
since both adminUser and User contain the key secret.

In your case I would remove the Secret field from User
and only have the one in adminUser.
Then whenever you need to write to the secret field,
make sure you use an adminUser.

答案2

得分: 1

另一种选择是声明一个接口。

type SecureModel interface {
    SecureMe()
}

确保你的模型实现了它:

type User struct {
    Id       bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
    Username string        `json:"username" bson:"username"`
    Secret   string        `json:"secret,omitempty" bson:"secret"`
}

func (u *User) SecureMe() {
    u.Secret = ""
}

然后根据调用的路由来调用它。

// 我被发送给非管理员,保护我。
if _, ok := user.(SecureModel); ok {
    user.(SecureModel).SecureMe()
}
// 转换为JSON,等等。
...

编辑:在这里使用接口的原因是为了在使用通用方法发送任意模型的情况下。

英文:

Another alternative would be to declare an interface.

type SecureModel interface {
    SecureMe()
}

Make sure your model implements it:

type User struct {
    Id       bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
    Username string        `json:"username" bson:"username"`
    Secret   string        `json:"secret,omitempty" bson:"secret"`
}

func (u *User) SecureMe() {
    u.Secret = ""
}

And only call it depending on which route is called.

// I am being sent to a non-admin, secure me.
if _, ok := user.(SecureModel); ok {
    user.(SecureModel).SecureMe()
}
// Marshall to JSON, etc.
...

Edit: The reason for using an interface here is for cases where you might send arbitrary models over the wire using a common method.

huangapple
  • 本文由 发表于 2013年10月10日 02:06:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/19279456.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定