Gaurav Ojha
Gaurav Ojha

Reputation: 1177

Use a pre-constructed string as bson.M for mgo query in go

The larger goal here is to have an update query in go set only those fields that are sent to it in the request.

For e.g., I have a document to update where I am allowing the user to update a variable number of fields by simply specifying them in the request like so -

{
"field1": valueOfField1,
"field2": valueOfField2,
"field3": valueOfField3,
...
}

The problem here is that when I decode this input using json.Decode to a custom struct type which has most fields optional, the values which do not exist in the input are left nil.

My struct looks like this -

type Fields struct {
    Field1       string      `bson:"field1" json:"field1,omitempty"`
    Field2       string      `bson:"field2" json:"field2"`
    Field3       time.Time   `bson:"field3,omitempty" json:"field3,omitempty"`
    Field4       bool        `bson:"field4,omitempty" json:"field4,omitempty"`
...
}

Now in my update query, I say,

bson.M{"$set": bson.M{"field1": body.Field1, "field2": body.Field2, "field3": body.Field3, "field4": body.Field4, ...}}

The problem is that if one of these fields does not exist in the input, it still over-writes existing values in the database and makes it null.

To avoid this, I would ideally want that this {"field1": body.Field1, "field2": body.Field2, "field3": body.Field3, "field4": body.Field4, ...} part is dynamically constructed depending on the fields coming in.

To do that, I did a json.Marshal of the input, like so -

finalbody, err := json.Marshal(body)

And then I am trying to use this inside the $set field as -

bson.M{"$set": string(finalbody)}

Of course this gives me an error saying - "Modifiers operate on fields but we found a string instead". The string is perfectly just like the bson.M would've been except that it is not a bson.M i.e. {"field1": valueOfField1, "field2": valueOfField2, "field3": valueOfField1, ...}

Where am I going wrong? [... represents 'and so on']

Upvotes: 2

Views: 2512

Answers (1)

Gaurav Ojha
Gaurav Ojha

Reputation: 1177

I found the solution by Unmarshalling finalbody into a map[string]interface{} and then using that as the update map.

So,

finalbody, err := json.Marshal(body)
var finalbodymap map[string]interface{}
json.Unmarshal(finalbody, &finalbodymap)

Of course, you need to add some error handling so the final code looks like this -

finalbody, err := json.Marshal(body)
    if err != nil {
        log.Println(err)
        return
    }
var finalbodymap map[string]interface{}
    if err = json.Unmarshal(finalbody, &finalbodymap); err != nil{
        log.Println(err)
    }

And then in the update query, I can simply write -

bson.M{"$set": finalbodymap}

One issue here though was that while Marshalling, it would set any time.Time type values to nil i.e. "0001-01-01T00:00:00Z". I suspect such behavior could be observed with certain other types as well.

Upvotes: 3

Related Questions