{ "aaBool": true, "aaInt": 1111, "aaStr": "Lorem", "map1": { "m1book": true, "m1int": 3333, "m1str": "m1Lorem" }, "map2": { "map3": { "m3int": 888, "m3str": "m3Lorem" } } }
package trigger_firestore import ( "cloud.google.com/go/firestore" _ "encoding/json" firebase "firebase.google.com/go/v4" "fmt" "github.com/GoogleCloudPlatform/functions-framework-go/functions" "github.com/cloudevents/sdk-go/v2/event" "github.com/googleapis/google-cloudevents-go/cloud/firestoredata" "golang.org/x/net/context" "google.golang.org/protobuf/proto" "log" "os" "reflect" "strings" ) // set the GOOGLE_CLOUD_PROJECT environment variable when deploying. var ( projectID = os.Getenv("GOOGLE_CLOUD_PROJECT") ) // client is a Firestore client, reused between function invocations. var client *firestore.Client func init() { // Use the application default credentials. conf := &firebase.Config{ProjectID: projectID} // Use context.Background() because the app/client should persist across // invocations. ctx := context.Background() app, err := firebase.NewApp(ctx, conf) if err != nil { log.Fatalf("firebase.NewApp: %v", err) } client, err = app.Firestore(ctx) if err != nil { log.Fatalf("app.Firestore: %v", err) } // Register cloud event function functions.CloudEvent("ProcessTrigger", ProcessTrigger) } func ProcessTrigger(ctx context.Context, event event.Event) error { var data firestoredata.DocumentEventData if err := proto.Unmarshal(event.Data(), &data); err != nil { return fmt.Errorf("proto.Unmarshal: %w", err) } fullPath := strings.Split(data.GetValue().GetName(), "/documents/")[1] pathParts := strings.Split(fullPath, "/") doc := strings.Join(pathParts[1:], "/") log.Printf("Doc: %+v\n", doc) log.Printf("Function triggered by change to: %v\n", event.Source()) log.Printf("Old value: %+v\n", data.GetOldValue()) log.Printf("New value: %+v\n", data.GetValue()) processUpdate(&data) return nil } func processUpdate(data *firestoredata.DocumentEventData) { updateMask := data.GetUpdateMask() log.Printf("Update Mask: %+v\n", updateMask) for _, fieldName := range updateMask.GetFieldPaths() { log.Printf("=================================") log.Printf("FieldName: %v", fieldName) log.Printf("FieldNameType: %v", reflect.TypeOf(fieldName)) fieldValue, ok := data.GetValue().GetFields()[fieldName] if ok { log.Printf("FieldValue: %v", fieldValue) stringSlice := strings.Split(fieldValue.String(), ":") log.Println(stringSlice) } } }
The above code works fine for updates to the top-level non-nested-fields (prefix aa) and I am able to print the fieldValue.
However for updates to nested fields (inside map1 and map3), ok is false.
How do I get the updated fieldValue for nested fields?
The logs generated are:
2024/04/14 14:55:44 Doc: testdoc 2024/04/14 14:55:44 Function triggered by change to: //firestore.googleapis.com/projects/myproj/databases/(default) 2024/04/14 14:55:44 Old value: name:"projects/myproj/databases/(default)/documents/testcollection/testdoc" fields:{key:"aaBool" value:{boolean_value:true}} fields:{key:"aaInt" value:{integer_value:11111}} fields:{key:"aaStr" value:{string_value:"Lorem"}} fields:{key:"map1" value:{map_value:{fields:{key:"m1book" value:{boolean_value:true}} fields:{key:"m1int" value:{integer_value:3333}} fields:{key:"m1str" value:{string_value:"m1Lorem"}}}}} fields:{key:"map2" value:{map_value:{fields:{key:"map3" value:{map_value:{fields:{key:"m3int" value:{integer_value:888}} fields:{key:"m3str" value:{string_value:"m3Lorem"}}}}}}}} create_time:{seconds:1713104864 nanos:548214000} update_time:{seconds:1713106076 nanos:80031000} 2024/04/14 14:55:44 New value: name:"projects/myproj/databases/(default)/documents/testcollection/testdoc" fields:{key:"aaBool" value:{boolean_value:false}} fields:{key:"aaInt" value:{integer_value:222}} fields:{key:"aaStr" value:{string_value:"LoremIpsum"}} fields:{key:"map1" value:{map_value:{fields:{key:"m1book" value:{boolean_value:false}} fields:{key:"m1int" value:{integer_value:4444}} fields:{key:"m1str" value:{string_value:"m1LoremIpsum"}}}}} fields:{key:"map2" value:{map_value:{fields:{key:"map3" value:{map_value:{fields:{key:"m3int" value:{integer_value:999}} fields:{key:"m3str" value:{string_value:"m3LoremIpsum"}}}}}}}} create_time:{seconds:1713104864 nanos:548214000} update_time:{seconds:1713106543 nanos:26415000} 2024/04/14 14:55:44 Update Mask: field_paths:"map2.map3.m3int" field_paths:"map2.map3.m3str" field_paths:"map1.m1str" field_paths:"map1.m1int" field_paths:"map1.m1book" field_paths:"aaStr" field_paths:"aaBool" field_paths:"aaInt" 2024/04/14 14:55:44 ================================= 2024/04/14 14:55:44 FieldName: map2.map3.m3int 2024/04/14 14:55:44 FieldNameType: string 2024/04/14 14:55:44 ================================= 2024/04/14 14:55:44 FieldName: map2.map3.m3str 2024/04/14 14:55:44 FieldNameType: string 2024/04/14 14:55:44 ================================= 2024/04/14 14:55:44 FieldName: map1.m1str 2024/04/14 14:55:44 FieldNameType: string 2024/04/14 14:55:44 ================================= 2024/04/14 14:55:44 FieldName: map1.m1int 2024/04/14 14:55:44 FieldNameType: string 2024/04/14 14:55:44 ================================= 2024/04/14 14:55:44 FieldName: map1.m1book 2024/04/14 14:55:44 FieldNameType: string 2024/04/14 14:55:44 ================================= 2024/04/14 14:55:44 FieldName: aaStr 2024/04/14 14:55:44 FieldNameType: string 2024/04/14 14:55:44 FieldValue: string_value:"LoremIpsum" 2024/04/14 14:55:44 [string_value "LoremIpsum"] 2024/04/14 14:55:44 ================================= 2024/04/14 14:55:44 FieldName: aaBool 2024/04/14 14:55:44 FieldNameType: string 2024/04/14 14:55:44 FieldValue: boolean_value:false 2024/04/14 14:55:44 [boolean_value false] 2024/04/14 14:55:44 ================================= 2024/04/14 14:55:44 FieldName: aaInt 2024/04/14 14:55:44 FieldNameType: string 2024/04/14 14:55:44 FieldValue: integer_value:222 2024/04/14 14:55:44 [integer_value 222]
In case required, this is how I deploy the trigger:
gcloud functions deploy test-trigger-1 \ --gen2 \ --runtime=go122 \ --region=us-west1 \ --trigger-location=us-west1 \ --source=. \ --entry-point=ProcessTrigger \ --min-instances=1 \ --memory=256Mi \ --set-env-vars=[GOOGLE_CLOUD_PROJECT=myproj] \ --trigger-event-filters=type=google.cloud.firestore.document.v1.written \ --trigger-event-filters=database='(default)' \ --trigger-event-filters-path-pattern=document='testcollection/{docID}'
Hi @gcpfirestore,
Welcome to Google Cloud Community!
The reason your code wasn't working for nested fields was because you were trying to access them directly from a flat map. Firestore uses nested maps for nested data.
The solution is to use the GetNested method provided by the firestoredata.DocumentEventData struct. This method takes the path segments of the nested field (separated by ".") and returns its value.
The deployment configuration you provided looks good. Make sure to update the GOOGLE_CLOUD_PROJECT environment variable with your actual project ID.
This fix should allow you to access and process updates for nested fields within your Firestore documents triggered by your Cloud Function.