Reputation: 1025
I am working on a project that uses combination of Go and MongoDB. I am stuck at a place where I have a struct like:
type Booking struct {
// booking fields
Id int `json:"_id,omitempty" bson:"_id,omitempty"`
Uid int `json:"uid,omitempty" bson:"uid,omitempty"`
IndustryId int `json:"industry_id,omitempty" bson:"industry_id,omitempty"`
LocationId int `json:"location_id,omitempty" bson:"location_id,omitempty"`
BaseLocationId int `json:"base_location_id,omitempty" bson:"base_location_id,omitempty"`
}
In this struct, the field Id
is of int
type. But as we know MongoDB's default id is of bsonObject
type. Some times, the system generates the default MongoDB id in Id
field.
To overcome this I have modified the struct like this:
type Booking struct {
// booking fields
Id int `json:"_id,omitempty" bson:"_id,omitempty"`
BsonId bson.ObjectId `json:"bson_id" bson:"_id,omitempty"`
Uid int `json:"uid,omitempty" bson:"uid,omitempty"`
IndustryId int `json:"industry_id,omitempty" bson:"industry_id,omitempty"`
LocationId int `json:"location_id,omitempty" bson:"location_id,omitempty"`
BaseLocationId int `json:"base_location_id,omitempty" bson:"base_location_id,omitempty"`
}
In above struct, I have mapped the same _id
field in two different struct fields Id (type int) and BsonId
(type bson.ObjectId
). I want if id of integer type comes, it maps under Id
otherwise under BsonId
.
But this thing is giving following error:
Duplicated key '_id' in struct models.Booking
How can I implement this type of thing with Go Structs ??
Update:
Here is the code I have written for custom marshaling/unmarshaling:
func (booking *Booking) SetBSON(raw bson.Raw) (err error) {
type bsonBooking Booking
if err = raw.Unmarshal((*bsonBooking)(booking)); err != nil {
return
}
booking.BsonId, err = booking.Id
return
}
func (booking *Booking) GetBSON() (interface{}, error) {
booking.Id = Booking.BsonId
type bsonBooking *Booking
return bsonBooking(booking), nil
}
But this is giving type mismatch error of Id and BsonId fields. What should I do now ?
Upvotes: 5
Views: 7353
Reputation: 417672
In your original struct you used this tag for the Id
field:
bson:"_id,omitempty"
This means if the value of the Id
field is 0
(zero value for the int
type), then that field will not be sent to MongoDB. But the _id
property is mandatory in MongoDB, so in this case the MongoDB server will generate an ObjectId
for it.
To overcome this, the easiest is to ensure the Id
will is always non-zero; or if the 0
is a valid id, remove the omitempty
option from the tag.
If your collection must allow ids of mixed type (int
and ObjectId
), then the easiest would be to define the Id
field with type of interface{}
so it can accomodate both (actually all) types of key values:
Id interface{} `json:"_id,omitempty" bson:"_id,omitempty"`
Yes, working with this may be a little more cumbersome (e.g. if you explicitly need the id as int
, you need to use type assertion), but this will solve your issue.
If you do need 2 id fields, one with int
type and another with ObjectId
type, then your only option will be to implement custom BSON marshaling and unmarshaling. This basically means to implement the bson.Getter
and / or bson.Setter
interfaces (one method for each) on your struct type, in which you may do anything you like to populate your struct or assemble the data to be actually saved / inserted. For details and example, see Accessing MongoDB from Go.
Here's an example how using custom marshaling it may look like:
Leave out the Id
and BsonId
fields from marshaling (using the bson:"-"
tag), and add a 3rd, "temporary" id field:
type Booking struct {
Id int `bson:"-"`
BsonId bson.ObjectId `bson:"-"`
TempId interface{} `bson:"_id"`
// rest of your fields...
}
So whatever id you have in your MongoDB, it will end up in TempId
, and only this id field will be sent and saved in MongoDB's _id
property.
Use the GetBSON()
method to set TempId
from the other id fields (whichever is set) before your struct value gets saved / inserted, and use SetBSON()
method to "copy" TempId
's value to one of the other id fields based on its dynamic type after the document is retrieved from MongoDB:
func (b *Booking) GetBSON() (interface{}, error) {
if b.Id != 0 {
b.TempId = b.Id
} else {
b.TempId = b.BsonId
}
return b, nil
}
func (b *Booking) SetBSON(raw bson.Raw) (err error) {
if err = raw.Unmarshal(b); err != nil {
return
}
if intId, ok := b.TempId.(int); ok {
b.Id = intId
} else bsonId, ok := b.TempId.(bson.ObjectId); ok {
b.BsonId = bsonId
} else {
err = errors.New("invalid or missing id")
}
return
}
Note: if you dislike the TempId
field in your Booking
struct, you may create a copy of Booking
(e.g. tempBooking
), and only add TempId
into that, and use tempBooking
for marshaling / unmarshaling. You may use embedding (tempBooking
may embed Booking
) so you can even avoid repetition.
Upvotes: 8