Get hands-on experience with 20+ free Google Cloud products and $300 in free credit for new customers.

Get updated values for nested fields in Firestore document

I am trying to implement a trigger (as a google cloud function) in golang that gets invoked when a firestore document is updated. I am following the documentation give here: https://cloud.google.com/functions/docs/calling/cloud-firestore
 
My firestore document is given below:
{
  "aaBool": true,
  "aaInt": 1111,
  "aaStr": "Lorem",
  "map1": {
    "m1book": true,
    "m1int": 3333,
    "m1str": "m1Lorem"
  },
  "map2": {
    "map3": {
      "m3int": 888,
      "m3str": "m3Lorem"
    }
  }
}
The code I have currently is:
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}'

 

 
 
 
 
5 1 495