Nested field update using golang struct

I am facing a issue with update document using golang mongo driver.
Scenario: I want to update a field that is nested in a struct. For ex: StructOuter -> structInner -> field1, field2, field3. Now if I want to update the field3 and I have the corresponding value as another struct, how can i go ahead by just updating this field alone. I tried with code below but it updates the whole structInner leaving only field3:

conv, _ := bson.Marshal(prod)
bson.Unmarshal(conv, &updateFields)
update := bson.M{
   "$set": updateFields,
}
model.SetUpdate(update). 

Just want to know if this is supported, if yes can you help me with it and also point to some deep dive links on this.

If you have a document like

{x: {y: {z: 1, other: 2}}}

I think the issue is that doing a $set will overwrite the entire subdocument, so if you only specify the value for z, the document will become

{x: {y: {z: <newValue>}}}

I think you can instead do

{$set: {x.y.z: 2}}

which will only set the z field and leave the other value unchanged.

Also, note that this question is not specific to the Go driver, as the driver just forwards your update document to the server. It may be helpful to instead provide an example of the existing document and an example of the updated document you want, and then others outside the driver team can help write the update syntax for your use case.

– Divjot

I am facing a issue with update document using golang mongo driver.
Scenario: I want to update a field that is nested in a struct. For ex: StructOuter -> structInner -> field1, field2, field3. Now if I want to update the field3 and I have the corresponding value as another struct, how can i go ahead by just updating this field alone. I tried with code below but it updates the whole structInner leaving only field3:

conv, _ := bson.Marshal(prod)
bson.Unmarshal(conv, &updateFields)
update := bson.M{
   "$set": updateFields,
}
model.SetUpdate(update). 

Adding Example:

Current Doc:
{
    "field_one": "value",
    "data": {
        "field_two": [
            "data1",
            "data2"
        ],
        "field_three": "check",
        "field_four": "abc",
        "field_five": "work",
    }
}

Update Request:
{
    "data": {
        "field_three": "check changed"
    }
}

Result Expected:
{
    "field_one": "value",
    "data": {
        "field_two": [
            "data1",
            "data2"
        ],
        "field_three": "check changed",
        "field_four": "abc",
        "field_five": "work",
    }
}

Result Got:
{
    "field_one": "value",
    "data": {
        "field_three": "check changed",
    }
}

Field changed is data.field_three. The update request has been populated in struct. but when the bson masrshall and unmarshall is used to form bson.M from it, the map created is multiple nested.

Just want to know if this is supported, if yes can you help me with it and also point to some deep dive links on this.

Hi @Ankush_Goyal,

If you have control over the code, you could try creating methods on the struct. For example, if you have the following structs:

type Outer struct {
    Data Inner 	`bson:"data"`
}

type Inner struct {
	FieldThree string `bson:"field_three"`
	FieldFour string `bson:"field_four"`
}

You can try adding methods as below to construct update statements. These are returned in the dot-notation format.

func (o *Outer) SetFieldThree(value string) bson.E {
	return bson.E{"data.field_three", value}
}
func (o *Outer) SetFieldFour(value string) bson.E {
	return bson.E{"data.field_four", value}
} 

To update, you can construct the statements like below:

x := Outer{}
var updateFields bson.D
updateFields = append(updateFields, x.SetFieldThree("updated"))
updateFields = append(updateFields, x.SetFieldFour("updated"))

statement := bson.D{{"$set", updateFields}} 
result, err := collection.UpdateOne(ctx, bson.M{}, statement)

Regards,
Wan.

Hi @wan,

Thanks for the description and example. But I was looking at some prewritten script as a part of mongo package.
After your reply, I wrote the below script (a bit specific to my use case) to generate keys(ex:- dat.field_three) for all the fields in n-level nested struct. Pasting it below:

func main() {
     // prod is a large struct
    var updateFields bson.M
    conv, _ := bson.Marshal(prod)
    bson.Unmarshal(conv, &updateFields)
    updateFields = ParseBsonMap(updateFields)
}


func ParseBsonMap(aMap map[string]interface{}) bson.M {
	finalMap := make(map[string]interface{})
	parseMap("", aMap, &finalMap)
	return finalMap
}

func parseMap(k string, aMap map[string]interface{}, finalMap *map[string]interface{}) {
	if len(aMap) == 0 {
		(*finalMap)[k] = nil
		return
	}

	for key, val := range aMap {
		if val != nil {
			switch concreteVal := val.(type) {
			case map[string]interface{}:
				parseMap(getKey(k, key), val.(map[string]interface{}), finalMap)
			case []interface{}:
				(*finalMap)[getKey(k, key)] = val.([]interface{})
			default:
				concreteValType := reflect.TypeOf(concreteVal)
				if concreteValType.Kind() == reflect.Map {
					parseMap(getKey(k, key), concreteVal.(primitive.M), finalMap)
				} else {
					(*finalMap)[getKey(k, key)] = concreteVal
				}
			}
		} else {
			(*finalMap)[getKey(k, key)] = nil
		}
	}
}

func getKey(k string, key string) string {
	if k == "" {
		return key
	}
	return k + "." + key
}

Thanks & Regards
Ankush Goyal

3 Likes

Updated to similar code to json
And when i tried to update instead of replacing the value it made and entry into the struct

Ex.
current struct
map[data:map[field_four:12 field_three:check field_two:[data1 data2]] field_one:value]

after passing to function struct
map[data:map[field_four:12 field_three:check field_two:[data1 data2]] data.field_three:check changed field_one:value]