Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unstructured MongoDB collections with mgo

Tags:

I'm VERY new to Go. From what I've seen in the examples of mGo, in order to query a collection and then read from it, you have to predefine the data that will be coming back in a struct.

type Person struct {
    ID        bson.ObjectId `bson:"_id,omitempty"`
    Name      string
    Phone     string
    Timestamp time.Time
}

In PHP, the document was assigned to an array. This was perfect as one record may have completely different set of keys (may not contain Name or Phone but contain Email) and I could access it directly without setting up a predefined class / struct / variable.

Is there a way to do the same in Go / mGo?

like image 958
kwolfe Avatar asked Aug 20 '13 16:08

kwolfe


2 Answers

There are multiple ways you can handle this.

Using a map:

var m bson.M
err := collection.Find(nil).One(&m)
check(err)
for key, value := range m {
    fmt.Println(key, value)
}

Note that there's nothing special about bson.M as far as mgo is concerned. It's just a map[string]interface{} type, and you can define your own map types and use them with mgo, even if they have a different value type.

Using a document slice:

The bson.D is a slice that is internally known to mgo, and it exists both to offer a more efficient mechanism and to offer a way to preserve the ordering of keys, which is used by MongoDB in some circumstances (for example, when defining indexes).

For example:

var d bson.D
err := collection.Find(nil).One(&d)
check(err)
for i, elem := range d {
    fmt.Println(elem.Name, elem.Value)
}

Using an ,inline map field

The ,inline bson flag can also be used in a map field, so that you can have your cake and eat it too. In other words, it enables using a struct so that manipulating known fields is convenient, and at the same time allows dealing with unknown fields via the inline map.

For example:

type Person struct {
    ID        bson.ObjectId `bson:"_id,omitempty"`
    Name      string
    Phone     string
    Extra     bson.M `bson:",inline"`
}
like image 162
Gustavo Niemeyer Avatar answered Oct 21 '22 01:10

Gustavo Niemeyer


You could store everything in a map. The mgo/bson package provides a bson.M data type that can be used to store arbitrary data and since MongoDB doesn't enforce a strong schema, mgo uses the bson.M type internally for everything.

If you just want to display the data, using a bson.M should be fine, but once you want to start working with it, you should consider using a struct instead. Otherwise, you would need a lot of type assertions in your program. For example, consider you want to print the title (result["title"]) of your document in upper case. By using just bson.M, your code would look like:

// is there a title attribute?
if title, ok := result["title"]; ok {
    // is it a string? (and not another map or integer or something else)
    if titleStr, ok := title.(string); ok {
        // ok, it is a string
        fmt.Println("Title: ", strings.ToUpper(titleStr))
    }
}

Your program would become much more readable and easier to maintain when you let mgo convert the data to a struct for you. Then, the same code might read as:

fmt.Println(strings.ToUpper(result.Title))

Normally you define one struct type for each type of document you want to deal with (i.e. one type for "users", another for "blog posts", etc.) that contains every attribute that you might want to access. If your user document does not happen to have an email address assigned, you will just get an empty string (or more generally, the zero value) back when you decode it.

like image 29
tux21b Avatar answered Oct 21 '22 01:10

tux21b