Go Mongo Driver Retrieve Schemaless Documents - mongodb

While working with Mongo Go Driver I want to retrieve Schemaless Documents.
I am able to retrieve documents using bson.M json:",inline" bson:",inline"
But this adds extra "M" key in json when i try to Decode to a struct
type Product struct {
ID primitive.ObjectID `bson:"_id"`
ProductId string `bson:"product_id" json:"product_id"`
bson.M `json:",inline" bson:",inline"`
}
Output:-
{
"id":"<ObjectId>",
"M":{
"some":""
}
}
But instead what i want how it is stored in Mongo.
{
"id":"<ObjectId>",
"some":""
}
I cant use directly something like this as I want to cast it to struct to work with some properties
var pr bson.M
err := p.FindOne(ctx, &p.options,query, &pr)
How can I remove that extra key which is getting added while converting schemaless Documents from Mongo?
Do I need to explicitly overwrite MarshalJSON() or is there something provided using Tags?

How can I remove that extra key which is getting added while converting schemaless Documents from Mongo?
You can just define a field mapping name, which will be flattened when marshalled. For example:
type Product struct {
ID primitive.ObjectID `bson:"_id"`
ProductId string `bson:"product_id"`
Others bson.M `bson:",inline"`
}
When you decode a document, you'll see that it will include other fields without the Others name. For example if you have a document:
{
"_id": ObjectId("5e8d330de85566f5a0557ea4"),
"product_id": "foo",
"some": "x",
"more": "y"
}
doc := Product{}
err = cur.Decode(&doc)
fmt.Println(doc)
// Outputs
// {ObjectID("5e8d330de85566f5a0557ea4") foo map[more:y some:x]}
I cant use directly something like this as I want to cast it to struct to work with some properties
You can use this directly for a query predicate. For example:
// After decoding 'doc' to product
var result bson.M
err := collection.FindOne(context.TODO(), doc).Decode(&result)
Tested using MongoDB Go driver v1.3.2
UPDATED:
If you would like to return JSON, you could use bson.MarshalExtJSON(). This should be easier as well in terms of dealing with objects that don't exist in JSON. i.e. ObjectId. For example:
// After decoding 'doc' to product
ejson, err := bson.MarshalExtJSON(doc, true, false)
fmt.Println(string(ejson))

Related

Update MongoDB array field in go

I have this data schema:
"person": { "name": "Neeraj", "hobbies": ["movies" ] }
This is the struct to insert the document in MongoDB
type Person struct {
Id primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
Name string `bson:"name,omitempty" json:"name,omitempty"`
Hobbies []string `json:"hobbies,omitempty" bson:"hobbies,omitempty"`
}
When PUT person API is called without the hobbies field, I would expect it to not update the hobbies field in DB. In this case when I parse the body, the struct has Hobbies set as null and because of 'omitempty' bson tag, it does not update the hobbies field in DB, which is fine.
filter := bson.D{{"_id", oid}}
update := bson.M{
"$set": record,
}
result, err := collection.UpdateOne(ctx, filter, update)
However, when PUT person API is called with the hobbies field set to an empty array, I would expect it to update the hobbies field and set it to empty. But in this case too, the record struct has hobbies field set to null, and it doesn't update the hobbies field.
How do I do this in go, so that my code caters to all of following update payload? I am using golang official mongo driver.
{
"name": "john" // Should not modify the hobbies field.
}
{
"name": "john",
"hobbies": [] // // Should update the hobbies field to an empty array
}
It sounds like you want to merge the person object based on what was provided in the request, whereas the MongoDB $set operator, "replaces the value of a field with the specified value." Because MongoDB doesn't understand how complex types should be merged, you'll have to specify the exact merging logic yourself.
filter := bson.M{"_id": oid}
set := bson.M{
"name": record.Name,
}
if (record.Hobbies) != nil {
set["hobbies"] = record.Hobbies
}
update := bson.M{"$set": set}

MongoDB BSON - Search on specific fields [duplicate]

How can I filter fields with the mongo-go-driver.
Tried it with findopt.Projection but no success.
type fields struct {
_id int16
}
s := bson.NewDocument()
filter := bson.NewDocument(bson.EC.ObjectID("_id", starterId))
var opts []findopt.One
opts = append(opts, findopt.Projection(fields{
_id: 0,
}))
staCon.collection.FindOne(nil, filter, opts...).Decode(s)
In the end, I want to suppress the field "_id". But the documents didn't change.
Edit: As the mongo-go driver evolved, it is possible to specify a projection using a simple bson.M like this:
options.FindOne().SetProjection(bson.M{"_id": 0})
Original (old) answer follows.
The reason why it doesn't work for you is because the field fields._id is unexported, and as such, no other package can access it (only the declaring package).
You must use a field name that is exported (starts with an uppercase latter), e.g. ID, and use struct tags to map it to the MongoDB _id field like this:
type fields struct {
ID int `bson:"_id"`
}
And now to perform a query using a projection:
projection := fields{
ID: 0,
}
result := staCon.collection.FindOne(
nil, filter, options.FindOne().SetProjection(projection)).Decode(s)
Note that you may also use a bson.Document as the projection, you don't need your own struct type. E.g. the following does the same:
projection := bson.NewDocument(
bson.EC.Int32("_id", 0),
)
result := staCon.collection.FindOne(
nil, filter, options.FindOne().SetProjection(projection)).Decode(s)

How to filter fields from a mongo document with the official mongo-go-driver

How can I filter fields with the mongo-go-driver.
Tried it with findopt.Projection but no success.
type fields struct {
_id int16
}
s := bson.NewDocument()
filter := bson.NewDocument(bson.EC.ObjectID("_id", starterId))
var opts []findopt.One
opts = append(opts, findopt.Projection(fields{
_id: 0,
}))
staCon.collection.FindOne(nil, filter, opts...).Decode(s)
In the end, I want to suppress the field "_id". But the documents didn't change.
Edit: As the mongo-go driver evolved, it is possible to specify a projection using a simple bson.M like this:
options.FindOne().SetProjection(bson.M{"_id": 0})
Original (old) answer follows.
The reason why it doesn't work for you is because the field fields._id is unexported, and as such, no other package can access it (only the declaring package).
You must use a field name that is exported (starts with an uppercase latter), e.g. ID, and use struct tags to map it to the MongoDB _id field like this:
type fields struct {
ID int `bson:"_id"`
}
And now to perform a query using a projection:
projection := fields{
ID: 0,
}
result := staCon.collection.FindOne(
nil, filter, options.FindOne().SetProjection(projection)).Decode(s)
Note that you may also use a bson.Document as the projection, you don't need your own struct type. E.g. the following does the same:
projection := bson.NewDocument(
bson.EC.Int32("_id", 0),
)
result := staCon.collection.FindOne(
nil, filter, options.FindOne().SetProjection(projection)).Decode(s)

Data not fully retrieved while using mgo pipe function

I was using pipe function in mgo to retrieve data. My data structs are as follows.
type Company struct {
Id bson.ObjectId `bson:"_id,omitempty"`
CompanyName string
Slug string
CompanyUsers []CompanyUser
}
type CompanyUser struct {
UserName string
Email string
FullName string
}
I needed to check whether a given "UserName" is present under "CompanyUsers" in a "Company" with a given "Slug".
Both Slug and UserName are provided by the User.
Using Pipe function I successfully done the search, but the data is returned with an empty CompanyUsers array.
My query is as follows:
var companyResults []Company
pipeline := []bson.M{
{"$match": bson.M{"slug": slug}},
{"$unwind": "$companyusers"},
{"$match": bson.M{
"companyusers.username": username,
}},
}
err := c.Pipe(pipeline).All(&companyResults)
This provides me a search result as follows:
[{ObjectIdHex("573aa0fddd731711c94830ca") MyCompany companyslug [] }]
None of the data in CompanyUsers are retrieved. How can I solve this error?
Try making companyResults a []map[string]interface{} instead of a []Company to see what sort of a result you get. This helps to figure out what the structure of companyResults should be.
var companyResults []map[string]interface{}
You will see a result like,
[map[companyname:MyCompany slug:companyslug companyusers:map[username:username] _id:test]]
See that companyusers is infact a map not an array. This is because your usage of the $unwind stage. It deconstructs the array, outputting one document for each element in the array. See docs.
I agree with John Smith's answer, that you do not need pipelines at all. But following should give you the desired result.
type Result struct {
Id string `bson:"_id,omitempty"`
CompanyName string
Slug string
CompanyUsers CompanyUser
}
var companyResults []Result
I might be missing something, but why do you need Pipe for this?
You probably could do it easier:
query := bson.M{"slug": slug, "companyusers.username": username}
err := c.Find(query).All(&companyResults)
I'm fairly certain this would give you the same results.
However, the main problem is you didn't give the bson marshaller the fields name. Since the field in your database is companyusers and username you'll have to tell that to the marshaller.
type Company struct {
Id bson.ObjectId `bson:"_id,omitempty"`
CompanyName string
Slug string
CompanyUsers []CompanyUser `bson:"companyuser"`
}
type CompanyUser struct {
UserName string `bson:"username"`
Email string
FullName string
}
You probably want to fix that for the rest of the fields as well.

how to use $push and $each with the go mgo driver?

I have a created a basic nested structure:
type Komplex struct {
count int `bson:"count"`
text string `bson:"text"`
}
type Parent struct {
Count int `bson:"count"`
SubCount []Komplex `bson:"subcount"`
}
And i would like to use mongo's safe update feature to extend a collection:
session.DB("test").C("ints").Upsert(bson.M{"count": toWrite.Count},
bson.M{"$addToSet": bson.M{"subcount": bson.M{"$each": toWrite.SubCount}}})
This works when i replace the Komplex struct with a slice of just int. However when i try to follow a complex set addition with my Komplex struct like described at https://docs.mongodb.org/manual/reference/operator/update/push/#up._S_push nothing gets inserted.
How do i properly marshal my slice with structs to bson.M in this situation?
Maybe work only for this code :
session.DB("test").C("ints").Update(bson.M{"count": toWrite.Count},
bson.M{"$addToSet": bson.M{"subcount": bson.M{"$each": toWrite.SubCount}}}
)
Your Golang code should be like:
data := model.Komplex {
count: 12345,
text: "yourText",
}
selector := bson.M{"count": toWrite.Count}
changes := bson.M{"$addToSet": bson.M{"subcount": bson.M{"$each": []model.Komplex{data}}}}
err = c.Update(selector, changes)
You have shared the reference link which deals with a $push but you are using $addToSet in your query.
$push - appends the array even if the data is duplicate
$addToSet- keeps only distinct values in the array
Also, I'd like to suggest you to include an _id field in the Parent struct.