unstructred inner document with mgo - mongodb

I have a document which has this following structure
{ "_id" : "736722976", "value" : { "total_visit" : 4, "FIFA World Cup 2014" : 1, "Germany" : 1, "Algeria" : 1, "Thomas Muller" : 1, "Mesut Ozil" : 1, "Monsoon" : 1, "India Meteorological Department (IMD)" : 1, "Web Exclusive" : 2, "Specials" : 1, "Tapas Pal" : 1, "Twitter Trends" : 1, "Sunanda Pushkar" : 1, "Shashi Tharoor" : 1, "AIIMS" : 1, "special" : 1 } }
THE MOST IMPORTANT thing is that the sub document structure under the key "value" is variable so I can not create a structure for that. I tried to follow the suggestion here - Unstructured MongoDB collections with mgo
And I came with this code ---
package main
import ("fmt"
"labix.org/v2/mgo" //importing mgo
"labix.org/v2/mgo/bson"
_ "reflect"
)
type AnalysisStruct struct{
Id string `bson:"_id,omitempty"`
Value bson.M `bson:",inline"`
}
func main() {
var m AnalysisStruct
//connecting to localhost mongodb
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
c := session.DB("my_analysis_db").C("analysis_mid_2")
iter := c.Find(nil).Iter()
for{
if iter.Next(&m){
fmt.Println(m.Value["value"]["total_visit"])
}else{
break
}
}
}
When I try to build this using go build -v -o analyzer it shows me this error---
./analyzer.go:32: invalid operation: m.Value["value"]["total_visit"] (index of type interface {})
I am terribly stuck with this. Can not get anything going. Please can somebody help?
Thanks
I cam up with this code after doing some research. Not the most optimized one for sure. But for my case it works. Took help from
http://blog.denevell.org/golang-interface-type-assertions-switch.html
https://groups.google.com/forum/#!topic/mgo-users/JYE-CP15az4
package main
import ("fmt"
"labix.org/v2/mgo" //importing mgo
"labix.org/v2/mgo/bson"
_ "reflect"
)
type AnalysisStruct struct{
Id string `bson:"_id,omitempty"`
Value bson.M `bson:",inline"`
}
func main() {
var m AnalysisStruct
//connecting to localhost mongodb
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
c := session.DB("consumergenepool_db").C("analysis_mid_2")
iter := c.Find(nil).Iter()
for{
if iter.Next(&m){
s := m.Value["value"].(bson.M)
data, _ := bson.Marshal(s)
var m bson.M
_ = bson.Unmarshal(data, &m)
fmt.Println(m)
for k, v := range m{
fmt.Print(k)
fmt.Print(" :: ")
fmt.Println(v)
}
}else{
break
}
}
}
Let me know your thoughts on this.
Thanks

When testing something new always use a lot of fmt.Printf's to get a feel for it, that being said.
Value bson.M `bson:",inline"`
Should be
Value bson.M `bson:"value,omitempty"`
And
fmt.Println(m.Value["value"]["total_visit"])
Should be:
fmt.Printf("%#v\n", m)
fmt.Println(m.Value["total_visit"])
Your m.Value is "value", so you can use m.Value["total_visit"] directly.
playground
//edit
You can only use inline to catch any fields that that aren't a part of the original struct, but since your struct only has 2 fields (Id and Value), you don't need it.
Now if you were to keep ,inline you would use it like this:
if v, ok := m.Value["value"].(bson.M); ok {
fmt.Println(v["total_visit"])
}
Because m.Value["value"] is an interface{}, you have to type assert it back to it's original value, bson.M before you could use it.

Related

How to get the value inside a primitive M

showInfoCursor, err := collection.Aggregate(context.TODO(), mongo.Pipeline{unwindStage, groupStage})
if err != nil {
panic(err)
}
var showsWithInfo []bson.M
if err = showInfoCursor.All(context.TODO(), &showsWithInfo); err != nil {
panic(err)
}
I'm iterating the showsWithInfo array. And each bson.M contains a primitive M type value for a particular key. I've tried to convert it to a struct, but it was no successful.
map[operatorId:1 channel: XYZ]
This is what I got once I print the value of that primitive M.
I need to get those two values from that. (operatorId, channel)
bson.M is a type alias to primitive.M:
type M = primitive.M
And primitive.M is a "simple" map:
type M map[string]interface{}
So you may index the value as you would any maps:
m := primitive.M{
"operatorId": 1,
"channel": "XYZ",
}
fmt.Println(m)
fmt.Println("Operator ID:", m["operatorId"])
fmt.Println("Channel:", m["channel"])
This outputs (try it on the Go Playground):
map[channel:XYZ operatorId:1]
Operator ID: 1
Channel: XYZ

bson.M {} deepequal does not seem to hande int32

I have a function for comparing two structs and making a bson document as input to mongodb updateOne()
Example struct format
type event struct {
...
Name string
StartTime int32
...
}
Diff function, please ignore that I have not checked for no difference yet.
func diffEvent(e event, u event) (bson.M, error) {
newValues := bson.M{}
if e.Name != u.Name {
newValues["name"] = u.Name
}
if e.StartTime != u.StartTime {
newValues["starttime"] = u.StartTime
}
...
return bson.M{"$set": newValues}, nil
}
Then I generated a test function like so:
func Test_diffEvent(t *testing.T) {
type args struct {
e event
u event
}
tests := []struct {
name string
args args
want bson.M
wantErr bool
}{
{
name: "update startime",
args: args{
e: event{StartTime: 1},
u: event{StartTime: 2},
},
want: bson.M{"$set": bson.M{"starttime": 2}},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := diffEvent(tt.args.e, tt.args.u)
if (err != nil) != tt.wantErr {
t.Errorf("diffEvent() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("diffEvent() = %v, want %v", got, tt.want)
}
})
}
}
This fails with a
--- FAIL: Test_diffEvent/update_startime (0.00s)
models_test.go:582: diffEvent() = map[$set:map[starttime:2]], want map[$set:map[starttime:2]]
For me this seem to be the same. I have played around with this and bool fields, string fields, enum fields, and fields as struct or fields as arrays of structs seems to work fine with deepequal, but it gives an error for int32 fields.
As a go beginner; what am I missing here? I would assume that if bool/string works then int32 would too.
This:
bson.M{"starttime": 2}
Sets the "starttime" key to the value of the literal 2. 2 is an untyped integer constant, and since no type is provided, its default type will be used which is int.
And 2 values stored in interface values are only equal if the dynamic value stored in them have identical type and value. So a value 2 with int type cannot be equal to a value 2 of type int32.
Use explicit type to tell you want to specify a value of int32 type:
bson.M{"starttime": int32(2)}

Converting MongoDB $max result to golang data

I try to get max values from MongoDB collection from my Go code.
What type should I use to decode result?
When I use bson.D{} as val2 type the result looks like [{_id <nil>} {max 66} {cnt 14}].
Here's the code:
filter := []bson.M{{
"$group": bson.M{
"_id": nil,
"max": bson.M{"$max": "$hellid"},
}},
}
cursor, err := collection.Aggregate(ctx, filter)
for cursor.Next(ctx) {
val2 := ???
err := cursor.Decode(&val2)
fmt.Printf("cursor: %v, value: %v\n", cursor.Current, val2)
}
}
Using bson.D already works as you presented. The problem may be you can't "easily" get out the max and cnt values.
Model your result document with a struct like this:
type result struct {
Max int `bson:"max"`
Count int `bson:"cnt"
}
Although cnt is not produced by the example code you provided.
And then:
var res result
err := cursor.Decode(&res)

How to retrieve []bson.M type of map

How to retrieve multidimensional []bson.M type of map
The data in mongo is like
"taskData" : {
"createdOn" : ISODate("2016-02-20T21:23:11.903Z"),
"Task_content" : "#bob",
"Priority" : "2",
"owner_Uname" : "alice"
}
The code through which i tried to access it
var n []bson.M
e := collection.Find(bson.M{"users."+strconv.Itoa(j)+".user_name" : r.FormValue("value[userName]")}).Select(bson.M{"taskData.owner_Uname":1,"_id":0}).All(&n)
if e != nil {
fmt.Println("Error : ",e)
}else{
fmt.Println(n[0]["taskData"])
}
getting output like this
map[owner_Uname:alice]
I need to access this resultant string with another query.
It is a interface i tried to convert it to simple map
newMap :=n[0]["taskData"].(map[string]interface{})but it gives me an runtime error interface conversion: interface {} is bson.M, not map[string]interface {}
result := rawData{}
err := collection.Find(bson.M{"user_name":n[0]["taskData"]["owner_Uname"]}).All(&result)
Now I want to use it in above query ...
Kindly help me out. Thanks in advance
Edit :-
The data in mongo is like
{
"_id" : ObjectId("56bf128f5a9a6a0ebfdd5075"),
"deadLine" : {
"Start_time" : ISODate("2016-05-24T00:00:00Z"),
"End_time" : ISODate("2016-05-29T00:00:00Z")
},
},
"taskData" : {
"createdOn" : ISODate("2016-02-20T21:23:11.903Z"),
"Task_content" : "#bob",
"Priority" : "2",
"owner_Uname" : "alice"
},
"group" : {
"1" : {
"grp_name" : "grp"
},
"2" : {
"grp_name" : "secondGrp"
}
}
That will work me too if it is done with nested struct or map in struct
I'll provide you with a general example to help you understand, since SO is not a free coding service, but a platform where peers help each other to take a grasp on the problem.
My approach is not to use bson.M at all for the returned value.
package main
import (
"fmt"
"time"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Baz struct {
Date time.Time
Value int
}
type Bar struct {
Name string
Baz []Baz
}
type Foo struct {
Owner string
hidden int
Bar Bar
}
const (
ds = "localhost:27017"
db = "test"
coll = "nestdemo"
)
func main() {
o := Foo{
Owner: "me",
hidden: 1,
Bar: Bar{
Name: "funky",
Baz: []Baz{
Baz{Date: time.Now(), Value: 42},
},
},
}
// CHECK ERRORS in production environments
conn, _ := mgo.Dial(ds)
defer conn.Close()
c := conn.DB(db).C(coll)
c.Insert(o)
l := &Foo{}
c.Find(bson.M{"owner": "me"}).One(l)
fmt.Printf("Loaded data: %+v\n", l)
fmt.Printf(
"You got your answer to life, the universe and all the rest at %s: %d\n",
l.Bar.Baz[0].Date.Format(time.Kitchen), l.Bar.Baz[0].Value,
)
}
You can run this program on you local machine (with the constants adjusted as needed), which should give you an output looking like this:
$ go run main.go
Loaded data: &{Owner:me hidden:0 Bar:{Name:funky Baz:[{Date:2016-02-24 09:00:06.471 +0100 CET Value:42}]}}
You got your answer to life, the universe and all the rest at 9:00AM: 42
The entry in the according collection should read something like this:
{
"_id" : ObjectId("56cd6306538ba56563bdab76"),
"owner" : "me",
"bar" : {
"name" : "funky",
"baz" : [
{
"date" : ISODate("2016-02-24T08:00:06.471Z"),
"value" : 42
}
]
}
}
A few things are to note here.
I didn't need a single character in my struct definitions to have the structs marshaled to and marshaled from BSON. It was done automagically by mgo according to the rules described in the docs. However, you can customize the behavior of the (un-)marshaling, as described there.
Unexported fields (hidden in this example) take their zero value upon unmarshalling – keep that in mind, it can bite you in the neck.
There is no need to use bson.M to handle your data, which makes life a lot easier – no manual type conversions, for example.
In summary: All you need to do is to create a struct your data can be unmarhaled into. Then you can access the various fields as usual, without string fiddling and alike. That's bit of work, but a rather trivial one, as you can see.
Note: The data model you have shown is both syntactically and conceptually incorrect. Setting aside the former for now: It is a very bad practice to have values as keys, as shown in the group subdoc. This will always force you to deal with string parsing back and forth, making your life with MongoDB as complicated as it can get as a developer.
My above suggestion assumes you will correct that into something like:
{
…
groups:[
{grp_id: 1, grp_name: "grp"},
{grp_id: 2, grp_name: "secondGrp"}
]
…
}

mgo find convert a single value array to string

This is part of my collection schema in mongodb:
{ "_id" : ObjectId("55e1eef5255da6d384754642"), "name" : [ "Web, Mobile & Software Dev", "Movil y desarrollo de software" ] } { "_id" : ObjectId("55e1f2d0255da6d38475464b"), "name" : [ "IT & Networking", "TI y Redes" ] } ...
Right now i can get the info like this:
err := r.Coll.Find(bson.M{}).Select(bson.M{"name": bson.M{"$slice": []int{1, 1}}}).All(&result.Data)
but i want "name" to return a string instead of a single value array, so i dont have to index it inside my frontend if no need.
very limited comments i need 2000 poits for editing my post and add more things it seems, this is not answer but maybe, so i have to loop it?, isnt better way?
err := r.Coll.Find(bson.M{}).Select(bson.M{"name": bson.M{"$slice": []int{1, 1}}}).All(&result.Data)
if err != nil {
return result, err
}
type skillnew struct {
Id bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
Name string `bson:"name,omitempty" json:"name,omitempty"`
}
skillsallnew := make([]skillnew, len(result.Data))
for i := range result.Data {
skillsallnew[i] = skillnew{result.Data[i].Id, result.Data[i].Name[0]}
}