最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Firestore Timestamp gets saved as Map - Stack Overflow

programmeradmin0浏览0评论

Following Snippet

const start = new Date(this.date + 'T' + this.time);
console.log(start); // Thu Sep 12 2019 04:00:00 GMT+0200

const tournament:Tournament = {
      start: firebase.firestore.Timestamp.fromDate(start)
}

Passing this tournament Object to a Callable Cloud Function which solo purpose is to save the tournament passed as a document will save the start field as a Map with the properties seconds and miliseconds instead of a Timestamp in Firestore.

I also tried to just do start: start but this also does not bring the desired result of a Timestamp being saved in Firestore.

Just for info this is how the Function code stripped down looks like:

firestore.collection('tournaments').doc(slugUrl).set(tournamentData)

(tournamentData is the passed Object from the frontend)

Following Snippet

const start = new Date(this.date + 'T' + this.time);
console.log(start); // Thu Sep 12 2019 04:00:00 GMT+0200

const tournament:Tournament = {
      start: firebase.firestore.Timestamp.fromDate(start)
}

Passing this tournament Object to a Callable Cloud Function which solo purpose is to save the tournament passed as a document will save the start field as a Map with the properties seconds and miliseconds instead of a Timestamp in Firestore.

I also tried to just do start: start but this also does not bring the desired result of a Timestamp being saved in Firestore.

Just for info this is how the Function code stripped down looks like:

firestore.collection('tournaments').doc(slugUrl).set(tournamentData)

(tournamentData is the passed Object from the frontend)

Share Improve this question asked Sep 12, 2019 at 0:05 niclas_4niclas_4 3,6841 gold badge24 silver badges51 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 6

Your Timestamp object must be serialized to JSON before it can be sent to the function. The default serialization breaks the Timestamp into its natural seconds and nanoseconds values the resulting object. All type information is lost.

In your function, you're going to have to read those individual values from the data passed to the function, convert them back into a proper Timestamp object using its two-argument constructor, then write that object to Cloud Firestore. Only then will it be saved as a timestamp type field.

I was able to resolve this by using msgpack instead of JSON for serialization because it has support for conversion of Temporal objects. Converting Firestore Timestamp objects was straightforward because they are based on Temporal Instant.

My use-case was sending Timestamp objects from one Cloud Function to another Cloud Function via PubSub. The example below is built around that concept but is general enough that it can be extended to other processes. (for example, sending data from a web app to a Cloud Function or vice versa)


First define a custom msgpack ExtensionCodec that can encode and decode Timestamp types, then register the extension with the Encoder and Decoder objects so that when they're used to serialize data they know how to handle those types.

The snippet below works best when turned into a package so that it can be shared between multiple functions/apps, otherwise it must be copied and pasted into each of the examples that e after it for them to work. I have omitted this snippet from each example below to keep them DRY.

import {
  Decoder,
  Encoder,
  ExtensionCodec,
  decodeTimestampToTimeSpec,
  encodeTimeSpecToTimestamp,
} from '@msgpack/msgpack'
import { Timestamp } from 'firebase-admin/firestore'

const firestoreTimestampType = 0
const extensionCodec: ExtensionCodec = new ExtensionCodec()

extensionCodec.register({
  type: firestoreTimestampType,
  encode(input: unknown): Uint8Array | null {
    if (input instanceof Timestamp) {
      const sec = input.seconds
      const nsec = input.nanoseconds

      return encodeTimeSpecToTimestamp({ sec, nsec })
    } else {
      return null
    }
  },
  decode(data: Uint8Array): Timestamp {
    const timeSpec = decodeTimestampToTimeSpec(data)
    const sec = timeSpec.sec
    const nsec = timeSpec.nsec

    return new Timestamp(sec, nsec)
  },
})

// Always use these instances to encode/decode objects that are or
// contain Timestamp type objects or else they will decode as Map
// objects the same way they do with JSON serialization
export const encoder = new Encoder({ extensionCodec })
export const decoder = new Decoder({ extensionCodec })

Next configure the function that sends data to encode it using the custom encoder:

import { type ClientConfig, PubSub } from '@google-cloud/pubsub'
import { Timestamp } from 'firebase-admin/firestore'

// Replace with your actual projectId and topic name
const pubSub = new PubSub('some-project-id' as ClientConfig).topic('some-topic-name')

interface CustomObject {
  timestamp: Timestamp
}

export const sendingCloudFunction = async (): Promise<void> => {
  const seconds = Math.floor(Date.now() / 1000)
  const nanoseconds = 0
  const timestamp: Timestamp = new Timestamp(seconds, nanoseconds)
  const customObject: CustomObject = { timestamp }

  // Encode the custom object with msgpack
  const encodedObject: Uint8Array = encoder.encode(customObject)

  // Pass this data on to the next function by publishing to PubSub
  const data: Buffer = Buffer.from(
    encodedObject.buffer,
    encodedObject.byteOffset,
    encodedObject.byteLength
  )
  await pubSub.publishMessage({ data })
}

Finally, configure the function that receives the data to decode it using the custom decoder:

import { type PubsubMessage } from '@google-cloud/pubsub/build/src/publisher'
import { Timestamp } from 'firebase-admin/firestore'

interface CustomObject {
  timestamp: Timestamp
}

export const receivingCloudFunction = async (
  message: PubsubMessage,
): Promise<void> => {
  if (message.data === null || message.data === undefined) {
    throw new Error('PubSub message must be defined')
  }

  // Decode the PubSub data back into a msgpack-encoded object
  const encodedObject: Buffer = Buffer.from(message.data.toString(), 'base64')

  // Decode the msgpack-encoded object back into CustomObject
  const decodedObject = decoder.decode(encodedObject) as CustomObject

  if (decodedObject.timestamp instanceof Timestamp) {
    console.log(`Successfully decoded ${decodedObject.timestamp.toDate().toString()} as type Timestamp`)
  }
}

When receivingCloudFunction is invoked it will log something similar to:

Successfully decoded Tue Mar 28 2023 12:02:11 GMT-0700 (Pacific Daylight Time) as type Timestamp

发布评论

评论列表(0)

  1. 暂无评论