我正在监听集合文档的更改事件,只是转储我收到的内容:
func ForwardUserChanged(ctx context.Context, e cloudfn.FirestoreEvent) error {
raw, err := json.Marshal(e.Value.Fields)
if err != nil {
return err
}
fmt.Println(string(raw))
return nil
}
其中
FirestoreEvent
是自定义结构:
// FirestoreEvent is the payload of a Firestore event.
type FirestoreEvent struct {
OldValue FirestoreValue `json:"oldValue"`
Value FirestoreValue `json:"value"`
UpdateMask struct {
FieldPaths []string `json:"fieldPaths"`
} `json:"updateMask"`
}
type FirestoreValue struct {
CreateTime time.Time `json:"createTime"`
Fields map[string]interface{} `json:"fields"`
Name string `json:"name"`
UpdateTime time.Time `json:"updateTime"`
}
我想要的是一种将
Fields
解码到我的结构中的简单方法,该结构之前已保存到同一个集合中。
问题是 Fields
看起来相当复杂,它不仅仅是将 map[string]interface{} 简单地映射到结构体字段。例如,Fields
看起来像这样:
{"answers":
{"mapValue":
{"fields":
{"fish-1":
{"mapValue":
{"fields":{"option":{"stringValue":"yes"},
但是原来的结构是
type Report struct {
Answers map[string]Answer
}
type Answer struct {
Option string
}
有没有一种简单的方法可以将映射反序列化到结构中?或者应该“手工”完成?
应该有一种方法可以从这些数据中获取
DocumentSnapshot
。来自 Firestore 的数据看起来像 protobuf 消息,甚至可以在 Document
的 google.golang.org/genproto/googleapis/firestore/v1
结构中看到。
在没有找到其他解决方案来处理事件字段后,我决定在这里发布我的解决方案,以防像我这样的其他人正在寻找。
请记住,我的解决方案存在一些问题,我只是还没有解决这些问题。
在某些时候,必须将其重构为递归调用,以便 mapValue 和 arrayValue 类型可以根据需要深入。
它应该有更好的错误检查功能。
type FirestoreValue struct {
CreateTime time.Time `json:"createTime"`
// Fields is the data for this value. The type depends on the format of your
// database. Log an interface{} value and inspect the result to see a JSON
// representation of your database fields.
Fields map[string]interface{} `json:"fields"` // I changed this to a map[string]interface{} instead of the example codes interface{}
Name string `json:"name"`
UpdateTime time.Time `json:"updateTime"`
}
func (fv *FirestoreValue) Recombobulate(destination interface{}) error {
result := make(map[string]interface{})
for fieldName, infVal := range fv.Fields {
for typeKey, val := range infVal.(map[string]interface{}) {
switch typeKey {
case "stringValue":
result[fieldName] = val
case "booleanValue":
result[fieldName] = val
case "integerValue":
// I saw firestore give me this once: integerValue: "1"
sVal, ok := val.(string)
if ok {
result[fieldName], _ = strconv.Atoi(sVal)
} else {
result[fieldName] = val
}
case "doubleValue":
result[fieldName] = val
case "timestampValue":
result[fieldName], _ = time.Parse(time.RFC3339, val.(string))
case "referenceValue": // this is just a string for all intents and purposes
result[fieldName] = val
case "nullValue":
// not really sure what to do with this one
result[fieldName] = val
case "arrayValue":
elements := val.(map[string]interface{})["values"]
var innards []interface{}
for _, ele := range elements.([]interface{}) {
for _, eleInterf := range ele.(map[string]interface{}) {
innards = append(innards, eleInterf)
}
}
result[fieldName] = innards
case "mapValue":
mapFields := val.(map[string]interface{})["fields"]
innards := make(map[string]interface{})
for mapKeyName, v := range mapFields.(map[string]interface{}) {
for _, innard := range v.(map[string]interface{}) {
innards[mapKeyName] = innard
}
}
result[fieldName] = innards
case "geoPointValue": // this is just a map[string]int/float
innards := make(map[string]interface{})
for mapKeyName, v := range val.(map[string]interface{}) {
innards[mapKeyName] = v
}
result[fieldName] = innards
}
}
}
mapstructure.Decode(result, &destination)
return nil
}
firestore SDK 中确实缺少此功能。我发布了 firestruct 包来解决这个问题。该包会递归地解开您的 Cloud Event 负载中包含的所有 Firestore 文档字段。
您可以选择将文档输出为您选择的结构体或类型安全的映射[字符串]接口{}
import (
"github.com/bennovw/firestruct"
)
func MyCloudFunction(ctx context.Context, e event.Event) error {
// Unmarshal the cloud event
cloudEvent := firestruct.FirestoreCloudEvent{}
err := json.Unmarshal(e.DataEncoded, &cloudEvent)
if err != nil {
fmt.Printf("Error unmarshalling firestore cloud event: %s", err)
return err
}
// Unmarshals the firestore document to your native Go type
// Nested maps and arrays are supported, strips any protojson data type tags
x := MyStruct{}
err = cloudEvent.DataTo(&x)
if err != nil {
fmt.Printf("Error converting firestore document to MyStruct: %s", err)
return err
}
return nil
}
处理 Cloud Firestore 的代码通常会手动将 DocumentSnapshot 中的值复制到您自己定义的数据结构中。
Java 代码是个例外,Firestore SDK 可以使用反射自动将字段值映射到 POJO 属性或从 POJO 属性映射字段值。 但并不是每个人都选择这个,因为他们可能需要修改传入和传出的值。 但我认为其他语言的 Firestore SDK 没有这种支持。