Is there an easy way to unmarshal Firestore data in protojson format into a map[string]interface{} or struct without all the protobuf data type tags? i.e. flatten the protojson data.
I have a Google cloud function that's triggered whenever a new Firebase document is created (a "cloud event"). This cloud event includes context information including the document being modified in protojson format:
import (
"google.golang.org/protobuf/encoding/protojson"
"github.com/davecgh/go-spew/spew"
)
func CloudFunction(ctx context.Context, e event.Event) error {
data := firestoredata.DocumentEventData{}
_ = protojson.Unmarshal(e.DataEncoded, &data);
spew.Dump(data)
}
--------Console Output--------
{
"oldValue": {},
"value": {
"createTime": "2023-03-30T00:00:00.000000Z",
"updateTime": "2023-03-30T00:00:00.000000Z",
"name": "projects/myproject/databases/(default)/documents/collectionname/00000000-0000-0000-0000-000000000000",
"fields": {
"ID": {
"stringValue": "00000000-0000-0000-0000-000000000000"
},
"action": {
"stringValue": "serverDoSomething"
},
"payload": {
"mapValue": {
"fields": {
"questionsList": {
"arrayValue": {
"values": [
{
"mapValue": {
"fields": {
"title": {
"stringValue": "How do I fly a kite?"
},
}
}
},
{
"mapValue": {
"fields": {
"title": {
"stringValue": "How do I fly a plane?"
},
}
}
}
]
}
}
}
}
}
}
},
"updateMask": {}
}
I want to marshal chunks of this protojson document into custom Go structs to easily validate each type of entry as shown below:
// CloudEventRequest is a struct that wraps around one or more data validation structs contained in the payload
CloudEventRequest {
ID: "00000000-0000-0000-0000-000000000000"
Action: "serverDoStuff"
Payload: map{
"questionsList": []Question{
Question{
Title: "How do I fly a kite?"
},
Question{
Title: "How do I fly a plane?"
}
}
}
}
The Firestore SDK includes a DataTo method to easily unmarshal the protojson formatted data into custom structs. I'm trying to do something very similar but already obtained the document data outside of the Firestore SDK.
// DataTo uses the document's fields to populate p, which can be a pointer to a map[string]interface{} or a pointer to a struct.
func (*firestore.DocumentSnapshot).DataTo(p interface{}) error
import (
"context"
"cloud.google.com/go/firestore"
)
func FirestoreRead(docEvent firestoredata.DocumentEventData) error {
ctx := context.Background()
ref := h.client.Collection("mycollection").Doc(docEvent.value.ID)
docSnapshot, err := tx.Get(ref)
dataValidationStruct := CloudEventRequest{}
err = docSnapshot.DataTo(&dataValidationStruct)
}
I wrote an open source Go package named "firestruct" to solve this challenge. You can find it here: github.com/bennovw/firestruct Your feedback and contributions are most welcome!
I ended up writing a function that recursively unwraps Firestore fields into a matching map. I then refactored the DataTo() method in cloud.google.com/go/firestore to unmarshal my map into a struct.