28

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

2 Answers 2

77

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.

Sign up to request clarification or add additional context in comments.

3 Comments

In this case, I am getting a runtime error: internal server error Duplicated key 'secret' in struct main.adminUser!
Updated my answer. I didn't realize there were duplicate keys involved.
Just as a side note for the record, the use of the ",inline" tag works even for normal fields (non-embedded/non-anonymous).
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.